As a proof-of-concept I performed multi-omic single-cell sequencing
to develop a computational pipeline for the robust identification of
tumor cells to support a study incorporating a larger cohort of patients
to understand both the molecular and genetic mechanisms of
lymphomagenesis, relapse/remission, and resistance to certain
therapeutics. Here, I prepared libraries for single-cell gene
expression, surface protein expression (antibody labelling/CITE-seq),
and T-cell receptor immune repertoire. https://www.10xgenomics.com/products/single-cell-immune-profiling
Small antibody panel designed primarily for identification of T-cells
(CD3, CD4, CD8, CD7, CD26)
#library(BiocManager)
library(dplyr)
library(shiny) # Trying to load Seurat may give an error that the shiny package is not loaded
library(Seurat)
library(patchwork)
library(tidyverse)
library(data.table)
library(SingleR)
library(celldex)
library(writexl)
library(utils)
library(leiden)
library(ggplot2)
library(RColorBrewer)
LOAD DATA
# Load the gene expression (GEX)/antibody-derived tag (ADT) data
CTCL.data <- Read10X("/home/bobby/RStudio/CTCL_ImmuneProfiling/sample_filtered_feature_bc_matrix/")
# Create Seurat Object
CTCL <- CreateSeuratObject(counts = CTCL.data[["Gene Expression"]], min.cells = 0, min.features = 200)
CTCL_CITE <- CreateSeuratObject(counts = CTCL.data[["Antibody Capture"]], min.cells = 0, min.features = 0)
CTCL[["CITE"]] <- CreateAssayObject(CTCL.data[["Antibody Capture"]][,colnames(x = CTCL)])
#For some reason orig.ident is set as SeuratProject
#Replace orig.ident with CTCL
CTCL@meta.data$orig.ident <- "CTCL"
DefaultAssay(CTCL) <- "CITE"
CTCL <- NormalizeData(CTCL, assay = "CITE", normalization.method = "CLR")
# Load in TCR data
tcr <- read.csv("/home/bobby/RStudio/CTCL_ImmuneProfiling/vdj_t/filtered_contig_annotations.csv")
tcr <- tcr[!duplicated(tcr$barcode), ] # Remove duplicates
tcr <- tcr[,c("barcode", "raw_clonotype_id")] # Only keep the barcode and clonotype columns
names(tcr)[names(tcr) == "raw_clonotype_id"] <- "clonotype_id"
tcr <- tcr[tcr$barcode %in% rownames(CTCL@meta.data),] # Remove barcodes not in seurat object
tcr$clonotype_id <- substring(tcr$clonotype_id, 10) # Remove "clonotype" from string
rownames(tcr) <- tcr[,1]
tcr[,1] <- NULL
tcr$clonotype_id <- str_pad(tcr$clonotype_id, 2, pad = "0") # Make all clonal assignments at least 2 characters
# Add TCR clonotype to the Seurat object's metadata.
CTCL@meta.data$tcr <- NULL
CTCL@meta.data$tcr <- "NA"
CTCL@meta.data$tcr[match(rownames(tcr), rownames(CTCL@meta.data))] <- tcr$clonotype_id
# Add in percent.mt metadata
DefaultAssay(CTCL) <- "RNA"
CTCL[["percent.mt"]] <- PercentageFeatureSet(CTCL, pattern = "^MT-")
saveRDS(CTCL, file = "/home/bobby/RStudio/CTCL_ImmuneProfiling/CTCL_ImmuneProfiling.rds")
QC AND CELL FILTERING
# Violin Plot QC metrics
VlnPlot(CTCL, features = c("nFeature_RNA", "nCount_RNA", "percent.mt"), group.by = "orig.ident", ncol = 3)
plot1 <- FeatureScatter(CTCL, feature1 = "nCount_RNA", feature2 = "percent.mt", group.by = "orig.ident")
plot2 <- FeatureScatter(CTCL, feature1 = "nCount_RNA", feature2 = "nFeature_RNA", group.by = "orig.ident")
plot1 + plot2
# Generate density plots for each QC metric
CTCL.meta.data <- CTCL@meta.data #Store metadata as a separate data matrix
ggplot(CTCL.meta.data, aes(x = nFeature_RNA, fill = "gray")) +
geom_density(alpha = 0.5) +
scale_x_continuous(breaks = round(seq(0, max(CTCL.meta.data$nFeature_RNA), by = 500), 0)) +
theme_bw() +
theme(legend.position = "none") +
ylab("Cell Density") +
scale_fill_manual(values = "firebrick")
ggplot(CTCL.meta.data, aes(x = nCount_RNA, fill = "gray")) +
scale_x_continuous(breaks = round(seq(0, max(CTCL.meta.data$nCount_RNA), by = 1000), 0)) +
geom_density(alpha = 0.5) +
theme_bw() +
theme(legend.position = "none",
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1)) +
ylab("Cell Density") +
scale_fill_manual(values = "firebrick")
ggplot(CTCL.meta.data, aes(x = percent.mt, fill = "gray")) +
geom_density(alpha = 0.5) +
theme_bw() +
theme(legend.position = "none") +
scale_x_continuous(breaks = round(seq(0, max(CTCL.meta.data$percent.mt), by = 1), 0)) +
ylab("Cell Density") +
scale_fill_manual(values = "firebrick")
BASED ON PLOTS, FILTER OUT CELLS WITH <200 nFeature_RNA or
>3000 nFeature_RNA or percent.mt > 8
# Filter out cells with fewer than 200 or greater than 2500 genes or percent mito greater than 8
ncol(CTCL) - ncol(subset(CTCL, subset = nFeature_RNA > 200 & nFeature_RNA < 2500 & percent.mt < 8)) # Num cells filtered out
# 929 cells removed from 6705 cells
CTCL <- subset(CTCL, subset = nFeature_RNA > 200 & nFeature_RNA < 2500 & percent.mt < 8)
saveRDS(CTCL, file = "/home/bobby/RStudio/CTCL_ImmuneProfiling/CTCL_ImmuneProfiling.rds")
NORMALIZE DATA
DefaultAssay(CTCL) <- "CITE"
CTCL <- NormalizeData(CTCL, assay = "CITE", normalization.method = "CLR")
DefaultAssay(CTCL) <- "RNA"
CTCL <- SCTransform(CTCL, vars.to.regress = "percent.mt")
# Save a copy for the snakemake pipeline
saveRDS(CTCL, file = "/home/bobby/RStudio/CTCL_ImmuneProfiling/CTCL_ImmuneProfiling_Snakemake.rds")
CTCL <- RunPCA(CTCL, verbose = FALSE)
saveRDS(CTCL, file = "/home/bobby/RStudio/CTCL_ImmuneProfiling/CTCL_ImmuneProfiling.rds")
USE ELBOW PLOT TO DETERMINE OPTIMAL NUMBER OF PCs
ElbowPlot(CTCL, ndims = 50) # Based on elbowplot, will carry forward 20 PCs. scTransform transformed data benefits from pushing the PC counts bc it is better at capturing true biological variability
PERFORM CLUSTERING, SAVE CLUSTERING DIMPLOTS, GENERATE HEATMAPS OF
TOP MARKER GENES FOR EACH CLUSTER ACROSS VARIOUS RESOLUTIONS
for (i in seq(from = 0.05, to = 0.5, by = 0.05)){
setwd("/home/bobby/RStudio/CTCL_ImmuneProfiling/LeidenUMAP")
CTCL <- FindClusters(CTCL, resolution = i, algorithm = 4)
png(filename = paste("Leiden", i, ".png", sep=""), width = 1000, height = 900)
LeidenPlot <- DimPlot(CTCL, label = TRUE, label.size = 8, reduction = "umap", group.by = paste("SCT_snn_res.", i, sep = ""), pt.size = 1.25)
print(LeidenPlot)
dev.off()
setwd("/home/bobby/RStudio/CTCL_ImmuneProfiling/LeidenTSNE")
png(filename = paste("Leiden", i, ".png", sep=""), width = 1000, height = 900)
LeidenPlot <- DimPlot(CTCL, label = TRUE, label.size = 8, reduction = "tsne", group.by = paste("SCT_snn_res.", i, sep = ""), pt.size = 1.25)
print(LeidenPlot)
dev.off()
CTCL.markers <- FindAllMarkers(CTCL, min.pct = 0.25, logfc.threshold = 0.25)
top10 <- CTCL.markers %>% group_by(cluster) %>% top_n(n = 10, wt = avg_log2FC)
top20 <- CTCL.markers %>% group_by(cluster) %>% top_n(n = 20, wt = avg_log2FC)
write.csv(top20, paste("/home/bobby/RStudio/CTCL_ImmuneProfiling/LeidenBiomarkers/LeidenRes", i, ".Top20Genes.csv", sep = ""), row.names = FALSE)
setwd("/home/bobby/RStudio/CTCL_ImmuneProfiling/LeidenHeatmap")
png(filename = paste("LeidenRes", i, ".png", sep=""), width = 1200, height = 1200)
LeidenHeatmap <- DoHeatmap(CTCL, features = top10$gene, size = 8) + NoLegend()
print(LeidenHeatmap)
dev.off()
}
saveRDS(CTCL, file = "/home/bobby/RStudio/CTCL_ImmuneProfiling/CTCL_ImmuneProfiling.rds")
SILHOUETTE ANALYSIS TO DETERMINE OPTIMAL CLUSTERING RESOLUTION
# Silhouette Analysis. This will require the cluster package
library(cluster)
SilScores <- data.frame(Res = character(10), AvgSilWidth = character(10))
dist.matrix <- dist(x = Embeddings(object = CTCL[["pca"]])[, 1:50])
for (i in 1:10){
CTCL <- SetIdent(CTCL, value = paste("SCT_snn_res.",i*0.05, sep = ""))
Clusters = Idents(CTCL)
sil <- silhouette(x = as.numeric(x = as.factor(x = Clusters)), dist = dist.matrix)
SilScores[i, 1] <- i*0.05
SilScores[i, 2] <- mean(sil[,3])
}
# Plot Silhouette scores as a function of leiden resolution
SilScores$Res <- as.numeric(SilScores$Res)
SilScores$AvgSilWidth <- as.numeric(SilScores$AvgSilWidth)
SilPlot <- ggplot(SilScores, aes(x = Res, y = AvgSilWidth)) +
geom_line(size = 1) +
geom_point(size = 3) +
scale_y_continuous(name = "AvgSilWidth", breaks = seq(0, max(SilScores$AvgSilWidth), by = 0.05)) +
scale_x_continuous(name = "Leiden Resolution", breaks = seq(0, 0.5, by = 0.05)) +
ggtitle("Silhouette Plot") +
theme_bw()
SilPlot
Based on silhouette scoring, a resolution of 0.05 is most optimal. We
can also evaluate this based on the heatmap, looking at which resolution
produces the highest number of transcriptionally distinct clusters,
however I find that using a statistical approach is best.
Note that the above analysis can also be performed across varying PCs
(runUMAP, FindNeighbors) and k.param (FindNeighbors) in addition to
resolution (FindClusters). So for instance, if I evaluated clustering
across 3 PCs, 3 k.param, and 10 resolutions I would generate and
evaluate 90 clustering iterations. In my experience I find that PCs and
k.param do not have a big impact on the final dataset, but tuning these
or other parameters should be considered depending on the dataset and
the needs of the project. To do this we can use the SnakeMake
pipeline.
Other alternative ways of evaluating optimal resolution is to
calculate a jaccard index using the scclusteval package, which is not
demonstrated here.
CELL CYCLE SCORING
There does not appear to bea ny particular cluster defined by
proliferating cells.
# A list of cell cycle markers, from Tirosh et al, 2015, is loaded with Seurat. We can
# segregate this list into markers of G2/M phase and markers of S phase
s.genes <- cc.genes$s.genes
g2m.genes <- cc.genes$g2m.gene
CTCL <- CellCycleScoring(CTCL, s.features = s.genes, g2m.features = g2m.genes, set.ident = TRUE)
DimPlot(CTCL, reduction = "umap", group.by = "Phase", pt.size = 0.25)
FeaturePlot(CTCL, reduction = "umap", feature = "G2M.Score", pt.size = 0.25)
FeaturePlot(CTCL, reduction = "umap", feature = "S.Score", pt.size = 0.25)
SingleR identification of cell types
SingleR_Ref <- list()
CTCL.Predict <- list()
SingleR_Ref[["HPCA_Ref"]] <- HumanPrimaryCellAtlasData()
SingleR_Ref[["DICE_Ref"]] <- DatabaseImmuneCellExpressionData()
SingleR_Ref[["ENCODE_Ref"]] <- BlueprintEncodeData()
ref <- c("HPCA_Ref", "DICE_Ref", "ENCODE_Ref")
CTCL <- SetIdent(CTCL, value = "SCT_snn_res.0.05")
for (i in 1:length(ref)) {
CTCL.Predict[[ref[i]]] <- SingleR(test = CTCL[["SCT"]]@data,
ref = SingleR_Ref[[ref[i]]],
clusters = CTCL[[]]$SCT_snn_res.0.05,
labels = SingleR_Ref[[ref[i]]]$label.fine,
assay.type.ref = "logcounts")
# The below uses the grid and gridExtra packages to add a title to each heatmap in order to distinguish them.
# The title is not ideally positioned but does the job, so is kept this way.
my_title <- textGrob(ref[i], gp = gpar(fontsize = 12, fontface = "bold"))
plot <- plotScoreHeatmap(CTCL.Predict[[ref[i]]], order.by = "clusters", clusters = rownames(CTCL.Predict[[ref[i]]]), cluster_cols = FALSE, silent = TRUE)
grid.arrange(grobs = list(my_title, plot[[4]]), heights = c(0.1, 1))
# Export scores in .csv format
write.csv(CTCL.Predict[[ref[i]]], paste("/home/bobby/RStudio/CTCL_ImmuneProfiling/SingleR/", ref[i], "_score.csv", sep = ""))
}
# Add in labels to seurat object
CTCL@meta.data$SingleR_HPCA_leiden_0.05 <- CTCL.Predict[["HPCA_Ref"]]$labels[match(CTCL[[]]$SCT_snn_res.0.05,
rownames(CTCL.Predict[["HPCA_Ref"]]))]
CTCL@meta.data$SingleR_DICE_leiden_0.05 <- CTCL.Predict[["DICE_Ref"]]$labels[match(CTCL[[]]$SCT_snn_res.0.05,
rownames(CTCL.Predict[["DICE_Ref"]]))]
CTCL@meta.data$SingleR_ENCODE_leiden_0.05 <- CTCL.Predict[["ENCODE_Ref"]]$labels[match(CTCL[[]]$SCT_snn_res.0.05,
rownames(CTCL.Predict[["ENCODE_Ref"]]))]
saveRDS(CTCL, file = "/home/bobby/RStudio/CTCL_ImmuneProfiling/CTCL_ImmuneProfiling.rds")
Generate plots
DimPlot_leiden0.05 <- DimPlot(CTCL,
reduction = "umap",
group.by = "SCT_snn_res.0.05",
label = TRUE,
pt.size = 0.1)
DimPlot_HPCA <- DimPlot(CTCL,
reduction = "umap",
group.by = "SingleR_HPCA_leiden_0.05",
label = FALSE,
pt.size = 0.1)
DimPlot_DICE <- DimPlot(CTCL,
reduction = "umap",
group.by = "SingleR_DICE_leiden_0.05",
label = FALSE,
pt.size = 0.1)
DimPlot_ENCODE <- DimPlot(CTCL,
reduction = "umap",
group.by = "SingleR_ENCODE_leiden_0.05",
label = FALSE,
pt.size = 0.1)
DimPlot_leiden0.05 + DimPlot_HPCA
DimPlot_leiden0.05 + DimPlot_DICE
DimPlot_leiden0.05 + DimPlot_ENCODE
EVALUATE TCR CLONALITY DATA
DimPlot(CTCL, reduction = "umap", group.by = "tcr", label = FALSE,pt.size = 0.1)
CTCL@meta.data$tcr %>% table() # Display total number of each TCR clonotype
# Display frequency of tcr clones relative to cluster ID
CTCL@meta.data[,c("SCT_snn_res.0.05", "tcr")] %>% group_by(SCT_snn_res.0.05, tcr) %>% summarise(n = n()) %>%
mutate(freq = n / sum(n))
Just visually based on the UMAP we can see that the most dominant
clone # 1 is mostly present in cluster 1. This is also backed up in the
relative frequency table, in which 74.4% (3647) of cluster 1 cells are
identified as TCR Clone 1. In cluster 2, the most frequent clone
identified is also clone 1 at 7.3% (4). For cluster 3, the most frequent
clone identified is also clone 1 at 11.2% (37).
In addition, SingleR analysis identifies clusters 1 and 3 as T-cells
consistently. Using HPCA and ENCODE references, these cells are
identified as TCM cells, the known cell-of-origin for CTCL. Based on
this result, cluster 1 is most likely malignant cells based on cell type
classification and TCR analysis. While cluster 3 may also be malignant
as well, the TCR data does not necessarily support this.
For the purposes of this analysis, cluster 1 will be considered the
dominant tumor clone. While this does omit some clone 1 cells identified
in cluster 2 and 3, these will be considered artifacts as the number of
these cells is small.
To add an additional layer of validation for tumor cell identity, we
can perform copy number alteration (CNA) analysis using inferCNV or
numbat. Both of these methods require a reference within the dataset.
For this dataset, only cluster 3 contains T-cells that are candidates
for normal. However, as they cluster so closely to the malignant cluster
1, there are some reasonable concerns about whether they are malignant
or not. While it is entirely possible for malignant cells to closely
mimic the gene expression profile of normal counterparts, in this case
we know that Sezary cells are highly atypical. In this context, I would
normally suggest integrating normal cells from another scRNA-seq
dataset. For simplicity sake, this is not done here and a numbat
analysis using cluster 1 as query cells and using cluster 3 as reference
is demonstrated below.
NUMBAT INTEGRATION
PRE-PROCESS SNP DATA
https://kharchenkolab.github.io/numbat/articles/numbat.html#preparing-data
Preparing data does not use much memory. It is best to run this in a
compute intensive instance (C5a, for instance).
Rscript /home/bobby/numbat/inst/bin/pileup_and_phase.R \
--label CTCL \
--samples CTCL \
--bams /home/bobby/RStudio/CTCL_ImmuneProfiling/numbat/preprocessing/sample_alignments.bam \
--barcodes /home/bobby/RStudio/CTCL_ImmuneProfiling/numbat/preprocessing/barcodes.tsv \
--outdir /home/bobby/RStudio/CTCL_ImmuneProfiling/numbat/preprocessing/ \
--gmap /home/bobby/Eagle_v2.4.1/tables/genetic_map_hg38_withX.txt.gz \
--snpvcf /home/bobby/numbat_data/genome1K.phase3.SNP_AF5e2.chr1toX.hg38.vcf \
--paneldir /home/bobby/numbat_data/1000G_hg38 \
--ncores 8
gzip -dk /home/bobby/RStudio/CTCL_ImmuneProfiling/numbat/preprocessing/CTCL_allele_counts.tsv.gz
PREPARE EXPRESSION DATA
Numbat takes a gene by cell integer UMI count matrix as input. You
can directly use results from upstream transcriptome quantification
pipelines such as 10x CellRanger.
library(numbat)
library(readr)
CTCL <- SetIdent(CTCL, value = "SCT_snn_res.0.05")
# Create seurat object for cells of interest
CTCL_numbat <- subset(CTCL, ident = 1)
# Create seurat object for reference cells
CTCL_numbat_ref <- subset(CTCL, ident = 3)
# Export count matrix for cells of interest
count_mat <- CTCL_numbat[["RNA"]]@counts
# Prepare reference count data. If using custom reference will need to use aggregate_count()
# cell_annot is a dataframe with columns "cell" and "group"
count_mat_ref <- CTCL_numbat_ref[["RNA"]]@counts
cell_annot <- data.frame(matrix(nrow = ncol(CTCL_numbat_ref), ncol = 0))
cell_annot$cell <- CTCL_numbat_ref %>% colnames()
cell_annot$group <- CTCL_numbat_ref@meta.data$orig.ident
ref_internal <- aggregate_counts(count_mat_ref, cell_annot)
rm(cell_annot)
saveRDS(count_mat, file = "/home/bobby/RStudio/CTCL_ImmuneProfiling/numbat/count_mat.rds")
saveRDS(count_mat_ref, file = "/home/bobby/RStudio/CTCL_ImmuneProfiling/numbat/count_mat_ref.rds")
saveRDS(ref_internal, file = "/home/bobby/RStudio/CTCL_ImmuneProfiling/numbat/ref_internal.rds")
RUN NUMBAT
library(numbat)
library(readr)
count_mat <- readRDS(file = "/home/bobby/RStudio/CTCL_ImmuneProfiling/numbat/count_mat.rds")
ref_internal <- readRDS(file = "/home/bobby/RStudio/CTCL_ImmuneProfiling/numbat/ref_internal.rds")
df_allele <- read_tsv(file = "/home/bobby/RStudio/CTCL_ImmuneProfiling/numbat/preprocessing/CTCL_allele_counts.tsv")
# run
out <- run_numbat(
count_mat, # gene x cell integer UMI count matrix
ref_internal, # reference expression profile, a gene x cell type normalized expression level matrix
df_allele, # allele dataframe generated by pileup_and_phase script
genome = "hg38",
t = 1e-5,
ncores = 8,
plot = TRUE,
max_entropy = 0.5,
out_dir = '/home/bobby/RStudio/CTCL_ImmuneProfiling/numbat/out/'
)
print(out)
Final results and clonal assignments is visualized below. Note that
clone 1 is defined as normal. Reference cells are not visualized.
# Visualize mutational history
nb <- Numbat$new(out_dir = '/home/bobby/RStudio/CTCL_ImmuneProfiling/numbat/out')
png(filename = "/home/bobby/RStudio/CTCL_ImmuneProfiling/numbat/plot_mut_history.png", width = 500, height = 150)
plot <- nb$plot_mut_history()
print(plot)
dev.off()
knitr::include_graphics("/home/bobby/RStudio/CTCL_ImmuneProfiling/numbat/out/bulk_clones_final.png")
knitr::include_graphics("/home/bobby/RStudio/CTCL_ImmuneProfiling/numbat/plot_mut_history.png")
Numbat is identifying deletions in chr 4, 6, 10 and amp in chr 11 and
19. Deletions in chr 10q are recurrent in CTCL (https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4552614/),
providing additional evidence that cluster 1 represents malignant cells.
Numbat clone 2 is identified as a pre-cursor cell population to clone 3.
Interestingly, most of the copy number events identified in this clone
are transcriptionally neutral, implying that these cells are
phenotypically “normal”. More investigation would need to be done to
confirm whether these are truly malignant precursors. The presence of
~113 “normal” cells may represent normal cells that closely resemble the
malignant cells, and may even be considered the cell of origin for the
tumor (although more evidence is needed to support this).
IMPORT NUMBAT RESULTS TO SEURAT OBJECT
# nb <- Numbat$new(out_dir = '/home/bobby/RStudio/CTCL_ImmuneProfiling/numbat/out')
nb_clonepost <- nb$clone_post
numbat_meta <- data.frame(matrix(ncol = 2, nrow = CTCL %>% ncol()))
colnames(numbat_meta) <- c("cell", "clone_opt")
numbat_meta$cell <- CTCL %>% colnames()
numbat_meta$clone_opt <- "NA" # All cells not contained within the seurat object will be labelled "NA" here
numbat_meta$clone_opt[match(nb_clonepost$cell, numbat_meta$cell)] <- nb_clonepost$clone_opt # Add in numbat clone label
row.names(numbat_meta) <- numbat_meta$cell
numbat_meta$cell <- NULL
CTCL@meta.data$numbat_clonepost <- numbat_meta$clone_opt
DimPlot(CTCL, reduction = "umap", group.by = "numbat_clonepost", cols = c("#E41A1C","#377EB8","#4DAF4A", "gray"), order = c("NA", "3", "2", "1"), pt.size = 0.05, raster = FALSE)
IMPORT SINGLE CELL CNV POSTERIORS TO SEURAT OBJECT
CNV posterior does not represent a “degree” of CNV but rather the
probability of that event occurring in a single cell. Here, we import
these CNV posteriors to the seurat object to visualize this using
standard seurat tools (FeaturePlot, DotPlot, VlnPlot, etc).
nb_joint_post <- nb$joint_post
muts <- nb$joint_post %>% distinct(seg)
muts <- muts$seg
cnv_type <- nb$joint_post %>% distinct(seg, cnv_state) %>% {setNames(.$cnv_state, .$seg)}
cnv_post_meta <- data.frame(matrix(0, ncol = length(muts), nrow = CTCL %>% ncol()))
rownames(cnv_post_meta) <- CTCL %>% colnames()
cnv_name <- paste(muts, cnv_type, "p", "cnv", sep = "_")
colnames(cnv_post_meta) <- cnv_name
for (i in 1:length(muts)) {
nb_joint_post_temp <- subset(nb_joint_post, seg == muts[i] & cnv_state == cnv_type[i])
cnv_post_meta[match(nb_joint_post_temp$cell, rownames(cnv_post_meta)),i] <- nb_joint_post_temp$p_cnv
rm(nb_joint_post_temp)
}
CTCL@meta.data <- cbind(CTCL@meta.data, cnv_post_meta) # Add into Seurat meta data
rm(cnv_post_meta)
rm(nb_joint_post)
# Visualize posteriors using VlnPlot and save plots
for (i in 1:length(muts)) {
setwd("/home/bobby/RStudio/CTCL_ImmuneProfiling/numbat/seurat/VlnPlot")
png(filename = paste(cnv_name[i], ".png", sep = ""), width = 900, height = 500)
plot <- VlnPlot(CTCL, features = cnv_name[i], group.by = "numbat_clonepost", raster = FALSE)
print(plot)
dev.off()
rm(plot)
setwd("/home/bobby")
}
# Dot plot representation is not very useful since every cell in the group will "express" the CNV, as numbat will assign a score to all cells
# Code is left here mostly for demonstration
DotPlot(CTCL,
features = CTCL@meta.data[,29:(29+length(muts)-1)] %>% colnames(),
group.by = "numbat_clonepost",
col.min = 0,
col.max = 1) +
theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust=1))
After importing numbat clone IDs to the seurat object, we can perform
an integrative analysis using numbat (CNV) and TCR
# Generate TCR + numbat clonepost combined dimplot
plot1 <- DimPlot(CTCL, reduction = "umap", group.by = "tcr", label = FALSE,pt.size = 0.1)
plot2 <- DimPlot(CTCL, reduction = "umap", group.by = "numbat_clonepost", cols = c("#E41A1C","#377EB8","#4DAF4A", "gray"), order = c("NA", "3", "2", "1"), pt.size = 0.05, raster = FALSE)
png(filename = "/home/bobby/RStudio/CTCL_ImmuneProfiling/tcr_numbatclone_dimplot.png", width = 1000, height = 450)
plot1 + plot2
dev.off()
rm(plot1)
rm(plot2)
knitr::include_graphics("/home/bobby/RStudio/CTCL_ImmuneProfiling/tcr_numbatclone_dimplot.png")
# Calculate frequency of tcr clone relative to numbat clone
TCR_freq <- CTCL@meta.data[, c("numbat_clonepost","tcr")] %>% table() %>% as.data.frame() %>% subset(Freq > 1) # Exclude clones with only 1 cell
palette <- colorRampPalette(colors = brewer.pal(12, "Paired"))(TCR_freq$tcr %>% unique() %>% length() - 1)
palette <- c(palette, "gray")
ggplot(TCR_freq, aes(x = numbat_clonepost, y = Freq, fill = tcr)) +
geom_col(colour = "black", position = "fill") +
scale_y_continuous(labels = scales::percent) +
scale_fill_manual(values = palette) +
ggtitle("TCR clone frequency by numbat clone")
# View relative frequency as a table
TCR_freq <- CTCL@meta.data[, c("numbat_clonepost","tcr")] %>% table() %>% as.data.frame()
TCR_freq %>%
group_by(numbat_clonepost) %>%
subset(Freq > 0) %>%
mutate(percent = Freq/sum(Freq)) %>%
arrange(numbat_clonepost, -percent)
Looking at the bar chart we see that numbat clone 1 (which is defined
as normal), actually contains a high percentage of tcr clone 1. However,
given that the number of cells in numbat clone 1 is rather small, this
may simply be an artifact and we also should keep in mind that the
majority of cells are still not tcr labelled.
Otherwise, we see that numbat clone 2 and 3 are both comprised of
~80% TCR clone 1, again providing evidence that these are, indeed, tumor
cells. Interestingly, the TCR data does not suggest the existence of two
clones as numbat is implying.
MONOCLE PSEUDOTIME ANALYSIS
Given that numbat is suggesting the existence of two tumor clones and
has inferred an evolutionary relationship between the two clones, we can
also use pseudotime analysis to address this. Here, we use monocle (https://cole-trapnell-lab.github.io/monocle3/docs/introduction/).
# Install monocle3 through the cole-trapnell-lab GitHub
# library("devtools")
# devtools::install_github('cole-trapnell-lab/leidenbase')
# devtools::install_github('cole-trapnell-lab/monocle3')
library(monocle3)
expression_matrix <- CTCL[["RNA"]]@counts
cell_metadata <- CTCL@meta.data[,c("tcr", "numbat_clonepost", "SCT_snn_res.0.05")]
gene_metadata <- data.frame(row.names = rownames(expression_matrix), gene_short_name = rownames(expression_matrix)) # This is required for new_cell_data_set() function
CTCL_monocle <- new_cell_data_set(expression_matrix,
cell_metadata = cell_metadata,
gene_metadata = gene_metadata)
# Pre-process the data. Note that a batch effect correction step is not included here
CTCL_monocle <- preprocess_cds(CTCL_monocle, num_dim = 20)
CTCL_monocle <- reduce_dimension(CTCL_monocle, reduction_method = "UMAP", preprocess_method = "PCA")
CTCL_monocle <- cluster_cells(CTCL_monocle, cluster_method = 'louvain')
plot_cells(CTCL_monocle, color_cells_by = "partition")
CTCL_monocle <- learn_graph(CTCL_monocle)
plot_cells(CTCL_monocle,
color_cells_by = "partition",
label_groups_by_cluster=FALSE,
label_leaves=FALSE,
label_branch_points=FALSE)
plot_cells(CTCL_monocle,
color_cells_by = "numbat_clonepost",
label_groups_by_cluster=FALSE,
label_leaves=FALSE,
label_branch_points=FALSE)
plot_cells(CTCL_monocle,
color_cells_by = "SCT_snn_res.0.05",
label_groups_by_cluster=FALSE,
label_leaves=FALSE,
label_branch_points=FALSE)
CTCL_monocle <- order_cells(CTCL_monocle)
plot_cells(CTCL_monocle,
color_cells_by = "pseudotime",
label_cell_groups=FALSE,
label_leaves=FALSE,
label_branch_points=FALSE,
graph_label_size=1.5)
saveRDS(CTCL_monocle, file = "/home/bobby/RStudio/CTCL_ImmuneProfiling/monocle/CTCL_monocle.rds")
# Export pseudotime calls to the Seurat object
pseudotime <- as.data.frame(pseudotime(CTCL_monocle, reduction_method = "UMAP"))
# Replace INF pseudotime with 0
pseudotime[,1] <- gsub(pattern = "Inf", replacement = 0, x = pseudotime$`pseudotime(CTCL_monocle, reduction_method = "UMAP")`)
pseudotime[,1] <- as.numeric(pseudotime$`pseudotime(CTCL_monocle, reduction_method = "UMAP")`)
CTCL <- AddMetaData(object = CTCL, metadata = pseudotime, col.name = "pseudotime_monocle")
Note that the below plots were edited to make labels more obvious and
to aggregate plots.
Note that monocle learns which cells are on a similar trajectory and
will place them in close proximity when performing UMAP, clustering, or
partitioning. While the UMAP will look different than that produced by
Seurat, the goals of the dimensionality reduction in each is
different.
Nonetheless, we can see that monocle clustering is mostly similar to
what we get by standard leiden clustering. Interestingly, monocle
identifies clusters cells from leiden cluster 1 and 3 together,
suggesting that there may be some cluster 1 cells that should be
assigned to cluster 3.
Most importantly, we see that monocle identifies a clear pseudotime
trajectory that connects numbat clone 2 and 3. And that numbat clones 2
and 3 cluster separately from numbat clone 1 and NA cells (leiden
cluster 2). This provides strong evidence that we chose the correct
reference for numbat analysis, but that in addition, the inferred clonal
evolutionary pattern (clone 2 –> 3) may indeed be real. Psuedotime
analysis further illustrates this point.
knitr::include_graphics("/home/bobby/RStudio/CTCL_ImmuneProfiling/monocle/monocle_combinedplots.png")

knitr::include_graphics("/home/bobby/RStudio/CTCL_ImmuneProfiling/monocle/monocle_psuedotime_plot.png")

LS0tCnRpdGxlOiAiTXVsdGktb21pYyBTaW5nbGUtQ2VsbCBSTkEtc2VxIEFuYWx5c2lzIG9mIGFuIGFnZ3Jlc3NpdmUgU2V6YXJ5IFN5bmRyb21lIFBhdGllbnQKICBTYW1wbGUiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgZGZfcHJpbnQ6IHBhZ2VkCmVkaXRvcl9vcHRpb25zOgogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUKLS0tCgpBcyBhIHByb29mLW9mLWNvbmNlcHQgSSBwZXJmb3JtZWQgbXVsdGktb21pYyBzaW5nbGUtY2VsbCBzZXF1ZW5jaW5nIHRvIGRldmVsb3AgYSBjb21wdXRhdGlvbmFsIHBpcGVsaW5lIGZvciB0aGUgcm9idXN0IGlkZW50aWZpY2F0aW9uIG9mIHR1bW9yIGNlbGxzIHRvIHN1cHBvcnQgYSBzdHVkeSBpbmNvcnBvcmF0aW5nIGEgbGFyZ2VyIGNvaG9ydCBvZiBwYXRpZW50cyB0byB1bmRlcnN0YW5kIGJvdGggdGhlIG1vbGVjdWxhciBhbmQgZ2VuZXRpYyBtZWNoYW5pc21zIG9mIGx5bXBob21hZ2VuZXNpcywgcmVsYXBzZS9yZW1pc3Npb24sIGFuZCByZXNpc3RhbmNlIHRvIGNlcnRhaW4gdGhlcmFwZXV0aWNzLiBIZXJlLCBJIHByZXBhcmVkIGxpYnJhcmllcyBmb3Igc2luZ2xlLWNlbGwgZ2VuZSBleHByZXNzaW9uLCBzdXJmYWNlIHByb3RlaW4gZXhwcmVzc2lvbiAoYW50aWJvZHkgbGFiZWxsaW5nL0NJVEUtc2VxKSwgYW5kIFQtY2VsbCByZWNlcHRvciBpbW11bmUgcmVwZXJ0b2lyZS4gaHR0cHM6Ly93d3cuMTB4Z2Vub21pY3MuY29tL3Byb2R1Y3RzL3NpbmdsZS1jZWxsLWltbXVuZS1wcm9maWxpbmcKClNtYWxsIGFudGlib2R5IHBhbmVsIGRlc2lnbmVkIHByaW1hcmlseSBmb3IgaWRlbnRpZmljYXRpb24gb2YgVC1jZWxscyAoQ0QzLCBDRDQsIENEOCwgQ0Q3LCBDRDI2KQoKYGBge3IgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CiNsaWJyYXJ5KEJpb2NNYW5hZ2VyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHNoaW55KSAjIFRyeWluZyB0byBsb2FkIFNldXJhdCBtYXkgZ2l2ZSBhbiBlcnJvciB0aGF0IHRoZSBzaGlueSBwYWNrYWdlIGlzIG5vdCBsb2FkZWQKbGlicmFyeShTZXVyYXQpCmxpYnJhcnkocGF0Y2h3b3JrKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KFNpbmdsZVIpCmxpYnJhcnkoY2VsbGRleCkKbGlicmFyeSh3cml0ZXhsKQpsaWJyYXJ5KHV0aWxzKQpsaWJyYXJ5KGxlaWRlbikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKYGBgCgpgYGB7ciBpbmNsdWRlID0gRkFMU0V9CkNUQ0wgPC0gcmVhZFJEUyhmaWxlID0gIi9ob21lL2JvYmJ5L1JTdHVkaW8vQ1RDTF9JbW11bmVQcm9maWxpbmcvQ1RDTF9JbW11bmVQcm9maWxpbmcucmRzIikKYGBgCgojIExPQUQgREFUQQoKYGBge3IgZXZhbCA9IEZBTFNFfQojIExvYWQgdGhlIGdlbmUgZXhwcmVzc2lvbiAoR0VYKS9hbnRpYm9keS1kZXJpdmVkIHRhZyAoQURUKSBkYXRhCkNUQ0wuZGF0YSA8LSBSZWFkMTBYKCIvaG9tZS9ib2JieS9SU3R1ZGlvL0NUQ0xfSW1tdW5lUHJvZmlsaW5nL3NhbXBsZV9maWx0ZXJlZF9mZWF0dXJlX2JjX21hdHJpeC8iKQoKIyBDcmVhdGUgU2V1cmF0IE9iamVjdApDVENMIDwtIENyZWF0ZVNldXJhdE9iamVjdChjb3VudHMgPSBDVENMLmRhdGFbWyJHZW5lIEV4cHJlc3Npb24iXV0sIG1pbi5jZWxscyA9IDAsIG1pbi5mZWF0dXJlcyA9IDIwMCkKQ1RDTF9DSVRFIDwtIENyZWF0ZVNldXJhdE9iamVjdChjb3VudHMgPSBDVENMLmRhdGFbWyJBbnRpYm9keSBDYXB0dXJlIl1dLCBtaW4uY2VsbHMgPSAwLCBtaW4uZmVhdHVyZXMgPSAwKQpDVENMW1siQ0lURSJdXSA8LSBDcmVhdGVBc3NheU9iamVjdChDVENMLmRhdGFbWyJBbnRpYm9keSBDYXB0dXJlIl1dWyxjb2xuYW1lcyh4ID0gQ1RDTCldKQoKI0ZvciBzb21lIHJlYXNvbiBvcmlnLmlkZW50IGlzIHNldCBhcyBTZXVyYXRQcm9qZWN0CiNSZXBsYWNlIG9yaWcuaWRlbnQgd2l0aCBDVENMCkNUQ0xAbWV0YS5kYXRhJG9yaWcuaWRlbnQgPC0gIkNUQ0wiCgpEZWZhdWx0QXNzYXkoQ1RDTCkgPC0gIkNJVEUiCkNUQ0wgPC0gTm9ybWFsaXplRGF0YShDVENMLCBhc3NheSA9ICJDSVRFIiwgbm9ybWFsaXphdGlvbi5tZXRob2QgPSAiQ0xSIikKCiMgTG9hZCBpbiBUQ1IgZGF0YQp0Y3IgPC0gcmVhZC5jc3YoIi9ob21lL2JvYmJ5L1JTdHVkaW8vQ1RDTF9JbW11bmVQcm9maWxpbmcvdmRqX3QvZmlsdGVyZWRfY29udGlnX2Fubm90YXRpb25zLmNzdiIpCnRjciA8LSB0Y3JbIWR1cGxpY2F0ZWQodGNyJGJhcmNvZGUpLCBdICMgUmVtb3ZlIGR1cGxpY2F0ZXMKdGNyIDwtIHRjclssYygiYmFyY29kZSIsICJyYXdfY2xvbm90eXBlX2lkIildICMgT25seSBrZWVwIHRoZSBiYXJjb2RlIGFuZCBjbG9ub3R5cGUgY29sdW1ucwpuYW1lcyh0Y3IpW25hbWVzKHRjcikgPT0gInJhd19jbG9ub3R5cGVfaWQiXSA8LSAiY2xvbm90eXBlX2lkIgp0Y3IgPC0gdGNyW3RjciRiYXJjb2RlICVpbiUgcm93bmFtZXMoQ1RDTEBtZXRhLmRhdGEpLF0gIyBSZW1vdmUgYmFyY29kZXMgbm90IGluIHNldXJhdCBvYmplY3QKdGNyJGNsb25vdHlwZV9pZCA8LSBzdWJzdHJpbmcodGNyJGNsb25vdHlwZV9pZCwgMTApICMgUmVtb3ZlICJjbG9ub3R5cGUiIGZyb20gc3RyaW5nCnJvd25hbWVzKHRjcikgPC0gdGNyWywxXQp0Y3JbLDFdIDwtIE5VTEwKdGNyJGNsb25vdHlwZV9pZCA8LSBzdHJfcGFkKHRjciRjbG9ub3R5cGVfaWQsIDIsIHBhZCA9ICIwIikgIyBNYWtlIGFsbCBjbG9uYWwgYXNzaWdubWVudHMgYXQgbGVhc3QgMiBjaGFyYWN0ZXJzCgojIEFkZCBUQ1IgY2xvbm90eXBlIHRvIHRoZSBTZXVyYXQgb2JqZWN0J3MgbWV0YWRhdGEuCkNUQ0xAbWV0YS5kYXRhJHRjciA8LSBOVUxMCkNUQ0xAbWV0YS5kYXRhJHRjciA8LSAiTkEiCkNUQ0xAbWV0YS5kYXRhJHRjclttYXRjaChyb3duYW1lcyh0Y3IpLCByb3duYW1lcyhDVENMQG1ldGEuZGF0YSkpXSA8LSB0Y3IkY2xvbm90eXBlX2lkCgojIEFkZCBpbiBwZXJjZW50Lm10IG1ldGFkYXRhCkRlZmF1bHRBc3NheShDVENMKSA8LSAiUk5BIgpDVENMW1sicGVyY2VudC5tdCJdXSA8LSBQZXJjZW50YWdlRmVhdHVyZVNldChDVENMLCBwYXR0ZXJuID0gIl5NVC0iKQoKc2F2ZVJEUyhDVENMLCBmaWxlID0gIi9ob21lL2JvYmJ5L1JTdHVkaW8vQ1RDTF9JbW11bmVQcm9maWxpbmcvQ1RDTF9JbW11bmVQcm9maWxpbmcucmRzIikKCmBgYAoKIyBRQyBBTkQgQ0VMTCBGSUxURVJJTkcKCmBgYHtyfQojIFZpb2xpbiBQbG90IFFDIG1ldHJpY3MgClZsblBsb3QoQ1RDTCwgZmVhdHVyZXMgPSBjKCJuRmVhdHVyZV9STkEiLCAibkNvdW50X1JOQSIsICJwZXJjZW50Lm10IiksIGdyb3VwLmJ5ID0gIm9yaWcuaWRlbnQiLCBuY29sID0gMykKcGxvdDEgPC0gRmVhdHVyZVNjYXR0ZXIoQ1RDTCwgZmVhdHVyZTEgPSAibkNvdW50X1JOQSIsIGZlYXR1cmUyID0gInBlcmNlbnQubXQiLCBncm91cC5ieSA9ICJvcmlnLmlkZW50IikKcGxvdDIgPC0gRmVhdHVyZVNjYXR0ZXIoQ1RDTCwgZmVhdHVyZTEgPSAibkNvdW50X1JOQSIsIGZlYXR1cmUyID0gIm5GZWF0dXJlX1JOQSIsIGdyb3VwLmJ5ID0gIm9yaWcuaWRlbnQiKQpwbG90MSArIHBsb3QyCgojIEdlbmVyYXRlIGRlbnNpdHkgcGxvdHMgZm9yIGVhY2ggUUMgbWV0cmljCkNUQ0wubWV0YS5kYXRhIDwtIENUQ0xAbWV0YS5kYXRhICNTdG9yZSBtZXRhZGF0YSBhcyBhIHNlcGFyYXRlIGRhdGEgbWF0cml4CmdncGxvdChDVENMLm1ldGEuZGF0YSwgYWVzKHggPSBuRmVhdHVyZV9STkEsIGZpbGwgPSAiZ3JheSIpKSArCiAgZ2VvbV9kZW5zaXR5KGFscGhhID0gMC41KSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHJvdW5kKHNlcSgwLCBtYXgoQ1RDTC5tZXRhLmRhdGEkbkZlYXR1cmVfUk5BKSwgYnkgPSA1MDApLCAwKSkgKwogIHRoZW1lX2J3KCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwogIHlsYWIoIkNlbGwgRGVuc2l0eSIpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSAiZmlyZWJyaWNrIikKCmdncGxvdChDVENMLm1ldGEuZGF0YSwgYWVzKHggPSBuQ291bnRfUk5BLCBmaWxsID0gImdyYXkiKSkgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSByb3VuZChzZXEoMCwgbWF4KENUQ0wubWV0YS5kYXRhJG5Db3VudF9STkEpLCBieSA9IDEwMDApLCAwKSkgKwogIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuNSkgKwogIHRoZW1lX2J3KCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSwgaGp1c3Q9MSkpICsKICB5bGFiKCJDZWxsIERlbnNpdHkiKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gImZpcmVicmljayIpCgpnZ3Bsb3QoQ1RDTC5tZXRhLmRhdGEsIGFlcyh4ID0gcGVyY2VudC5tdCwgZmlsbCA9ICJncmF5IikpICsKICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAwLjUpICsKICB0aGVtZV9idygpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gcm91bmQoc2VxKDAsIG1heChDVENMLm1ldGEuZGF0YSRwZXJjZW50Lm10KSwgYnkgPSAxKSwgMCkpICsKICB5bGFiKCJDZWxsIERlbnNpdHkiKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gImZpcmVicmljayIpCgpgYGAKCkJBU0VEIE9OIFBMT1RTLCBGSUxURVIgT1VUIENFTExTIFdJVEggPDIwMCBuRmVhdHVyZV9STkEgb3IgPjMwMDAgbkZlYXR1cmVfUk5BIG9yIHBlcmNlbnQubXQgPiA4CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQojIEZpbHRlciBvdXQgY2VsbHMgd2l0aCBmZXdlciB0aGFuIDIwMCBvciBncmVhdGVyIHRoYW4gMjUwMCBnZW5lcyBvciBwZXJjZW50IG1pdG8gZ3JlYXRlciB0aGFuIDgKbmNvbChDVENMKSAtIG5jb2woc3Vic2V0KENUQ0wsIHN1YnNldCA9IG5GZWF0dXJlX1JOQSA+IDIwMCAmIG5GZWF0dXJlX1JOQSA8IDI1MDAgJiBwZXJjZW50Lm10IDwgOCkpICMgTnVtIGNlbGxzIGZpbHRlcmVkIG91dAoKIyA5MjkgY2VsbHMgcmVtb3ZlZCBmcm9tIDY3MDUgY2VsbHMKQ1RDTCA8LSBzdWJzZXQoQ1RDTCwgc3Vic2V0ID0gbkZlYXR1cmVfUk5BID4gMjAwICYgbkZlYXR1cmVfUk5BIDwgMjUwMCAmIHBlcmNlbnQubXQgPCA4KQoKc2F2ZVJEUyhDVENMLCBmaWxlID0gIi9ob21lL2JvYmJ5L1JTdHVkaW8vQ1RDTF9JbW11bmVQcm9maWxpbmcvQ1RDTF9JbW11bmVQcm9maWxpbmcucmRzIikKCmBgYAoKIyBOT1JNQUxJWkUgREFUQQoKYGBge3IgZXZhbCA9IEZBTFNFfQpEZWZhdWx0QXNzYXkoQ1RDTCkgPC0gIkNJVEUiCkNUQ0wgPC0gTm9ybWFsaXplRGF0YShDVENMLCBhc3NheSA9ICJDSVRFIiwgbm9ybWFsaXphdGlvbi5tZXRob2QgPSAiQ0xSIikKCkRlZmF1bHRBc3NheShDVENMKSA8LSAiUk5BIgpDVENMIDwtIFNDVHJhbnNmb3JtKENUQ0wsIHZhcnMudG8ucmVncmVzcyA9ICJwZXJjZW50Lm10IikKCiMgU2F2ZSBhIGNvcHkgZm9yIHRoZSBzbmFrZW1ha2UgcGlwZWxpbmUKc2F2ZVJEUyhDVENMLCBmaWxlID0gIi9ob21lL2JvYmJ5L1JTdHVkaW8vQ1RDTF9JbW11bmVQcm9maWxpbmcvQ1RDTF9JbW11bmVQcm9maWxpbmdfU25ha2VtYWtlLnJkcyIpCgpDVENMIDwtIFJ1blBDQShDVENMLCB2ZXJib3NlID0gRkFMU0UpCgpzYXZlUkRTKENUQ0wsIGZpbGUgPSAiL2hvbWUvYm9iYnkvUlN0dWRpby9DVENMX0ltbXVuZVByb2ZpbGluZy9DVENMX0ltbXVuZVByb2ZpbGluZy5yZHMiKQoKYGBgCgojIFVTRSBFTEJPVyBQTE9UIFRPIERFVEVSTUlORSBPUFRJTUFMIE5VTUJFUiBPRiBQQ3MKCmBgYHtyfQpFbGJvd1Bsb3QoQ1RDTCwgbmRpbXMgPSA1MCkgIyBCYXNlZCBvbiBlbGJvd3Bsb3QsIHdpbGwgY2FycnkgZm9yd2FyZCAyMCBQQ3MuIHNjVHJhbnNmb3JtIHRyYW5zZm9ybWVkIGRhdGEgYmVuZWZpdHMgZnJvbSBwdXNoaW5nIHRoZSBQQyBjb3VudHMgYmMgaXQgaXMgYmV0dGVyIGF0IGNhcHR1cmluZyB0cnVlIGJpb2xvZ2ljYWwgdmFyaWFiaWxpdHkKYGBgCgojIFBFUkZPUk0gVU1BUCBBTkQgVFNORSBESU1FTlNJT05BTElUWSBSRURVQ1RJT04KCmBgYHtyLCBldmFsID0gRkFMU0V9CiMgVXNpbmcgdGhlIHByZXZpb3VzbHkgZGV0ZXJtaW5lZCBkaW1lbnNpb25hbGl0eSwgcGVyZm9ybSBVTUFQIGFuZCB0U05FCkNUQ0wgPC0gUnVuVU1BUChDVENMLCBkaW1zID0gMToyMCkKQ1RDTCA8LSBGaW5kTmVpZ2hib3JzKENUQ0wsIGRpbXMgPSAxOjIwKQpEaW1QbG90KENUQ0wsIGxhYmVsID0gRkFMU0UsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgZ3JvdXAuYnkgPSAib3JpZy5pZGVudCIsIHB0LnNpemUgPSAwLjI1KQpEaW1QbG90KENUQ0wsIGxhYmVsID0gRkFMU0UsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgZ3JvdXAuYnkgPSAidGNyIiwgcHQuc2l6ZSA9IDAuMjUpIApDVENMIDwtIFJ1blRTTkUoQ1RDTCwgZGltcyA9IDE6MjApCkRpbVBsb3Qob2JqZWN0ID0gQ1RDTCwgbGFiZWwgPSBGQUxTRSwgcmVkdWN0aW9uID0gInRzbmUiLCBncm91cC5ieSA9ICJvcmlnLmlkZW50IiwgcHQuc2l6ZSA9IDAuMjUpCkRpbVBsb3Qob2JqZWN0ID0gQ1RDTCwgbGFiZWwgPSBGQUxTRSwgcmVkdWN0aW9uID0gInRzbmUiLCBncm91cC5ieSA9ICJ0Y3IiLCBwdC5zaXplID0gMC4yNSkgCmBgYAoKIyBQRVJGT1JNIENMVVNURVJJTkcsIFNBVkUgQ0xVU1RFUklORyBESU1QTE9UUywgR0VORVJBVEUgSEVBVE1BUFMgT0YgVE9QIE1BUktFUiBHRU5FUyBGT1IgRUFDSCBDTFVTVEVSIEFDUk9TUyBWQVJJT1VTIFJFU09MVVRJT05TCgpgYGB7ciBldmFsID0gRkFMU0V9CmZvciAoaSBpbiBzZXEoZnJvbSA9IDAuMDUsIHRvID0gMC41LCBieSA9IDAuMDUpKXsKICBzZXR3ZCgiL2hvbWUvYm9iYnkvUlN0dWRpby9DVENMX0ltbXVuZVByb2ZpbGluZy9MZWlkZW5VTUFQIikKICBDVENMIDwtIEZpbmRDbHVzdGVycyhDVENMLCByZXNvbHV0aW9uID0gaSwgYWxnb3JpdGhtID0gNCkKICBwbmcoZmlsZW5hbWUgPSBwYXN0ZSgiTGVpZGVuIiwgaSwgIi5wbmciLCBzZXA9IiIpLCB3aWR0aCA9IDEwMDAsIGhlaWdodCA9IDkwMCkKICBMZWlkZW5QbG90IDwtIERpbVBsb3QoQ1RDTCwgbGFiZWwgPSBUUlVFLCBsYWJlbC5zaXplID0gOCwgcmVkdWN0aW9uID0gInVtYXAiLCBncm91cC5ieSA9IHBhc3RlKCJTQ1Rfc25uX3Jlcy4iLCBpLCBzZXAgPSAiIiksIHB0LnNpemUgPSAxLjI1KQogIHByaW50KExlaWRlblBsb3QpCiAgZGV2Lm9mZigpCiAgc2V0d2QoIi9ob21lL2JvYmJ5L1JTdHVkaW8vQ1RDTF9JbW11bmVQcm9maWxpbmcvTGVpZGVuVFNORSIpCiAgcG5nKGZpbGVuYW1lID0gcGFzdGUoIkxlaWRlbiIsIGksICIucG5nIiwgc2VwPSIiKSwgd2lkdGggPSAxMDAwLCBoZWlnaHQgPSA5MDApCiAgTGVpZGVuUGxvdCA8LSBEaW1QbG90KENUQ0wsIGxhYmVsID0gVFJVRSwgbGFiZWwuc2l6ZSA9IDgsIHJlZHVjdGlvbiA9ICJ0c25lIiwgZ3JvdXAuYnkgPSBwYXN0ZSgiU0NUX3Nubl9yZXMuIiwgaSwgc2VwID0gIiIpLCBwdC5zaXplID0gMS4yNSkKICBwcmludChMZWlkZW5QbG90KQogIGRldi5vZmYoKQogIENUQ0wubWFya2VycyA8LSBGaW5kQWxsTWFya2VycyhDVENMLCBtaW4ucGN0ID0gMC4yNSwgbG9nZmMudGhyZXNob2xkID0gMC4yNSkKICB0b3AxMCA8LSBDVENMLm1hcmtlcnMgJT4lIGdyb3VwX2J5KGNsdXN0ZXIpICU+JSB0b3BfbihuID0gMTAsIHd0ID0gYXZnX2xvZzJGQykKICB0b3AyMCA8LSBDVENMLm1hcmtlcnMgJT4lIGdyb3VwX2J5KGNsdXN0ZXIpICU+JSB0b3BfbihuID0gMjAsIHd0ID0gYXZnX2xvZzJGQykKICB3cml0ZS5jc3YodG9wMjAsIHBhc3RlKCIvaG9tZS9ib2JieS9SU3R1ZGlvL0NUQ0xfSW1tdW5lUHJvZmlsaW5nL0xlaWRlbkJpb21hcmtlcnMvTGVpZGVuUmVzIiwgaSwgIi5Ub3AyMEdlbmVzLmNzdiIsIHNlcCA9ICIiKSwgcm93Lm5hbWVzID0gRkFMU0UpCiAgc2V0d2QoIi9ob21lL2JvYmJ5L1JTdHVkaW8vQ1RDTF9JbW11bmVQcm9maWxpbmcvTGVpZGVuSGVhdG1hcCIpCiAgcG5nKGZpbGVuYW1lID0gcGFzdGUoIkxlaWRlblJlcyIsIGksICIucG5nIiwgc2VwPSIiKSwgd2lkdGggPSAxMjAwLCBoZWlnaHQgPSAxMjAwKQogIExlaWRlbkhlYXRtYXAgPC0gRG9IZWF0bWFwKENUQ0wsIGZlYXR1cmVzID0gdG9wMTAkZ2VuZSwgc2l6ZSA9IDgpICsgTm9MZWdlbmQoKQogIHByaW50KExlaWRlbkhlYXRtYXApCiAgZGV2Lm9mZigpCn0KCnNhdmVSRFMoQ1RDTCwgZmlsZSA9ICIvaG9tZS9ib2JieS9SU3R1ZGlvL0NUQ0xfSW1tdW5lUHJvZmlsaW5nL0NUQ0xfSW1tdW5lUHJvZmlsaW5nLnJkcyIpCmBgYAoKIyBTSUxIT1VFVFRFIEFOQUxZU0lTIFRPIERFVEVSTUlORSBPUFRJTUFMIENMVVNURVJJTkcgUkVTT0xVVElPTgoKYGBge3J9CiMgU2lsaG91ZXR0ZSBBbmFseXNpcy4gVGhpcyB3aWxsIHJlcXVpcmUgdGhlIGNsdXN0ZXIgcGFja2FnZQpsaWJyYXJ5KGNsdXN0ZXIpClNpbFNjb3JlcyA8LSBkYXRhLmZyYW1lKFJlcyA9IGNoYXJhY3RlcigxMCksIEF2Z1NpbFdpZHRoID0gY2hhcmFjdGVyKDEwKSkKZGlzdC5tYXRyaXggPC0gZGlzdCh4ID0gRW1iZWRkaW5ncyhvYmplY3QgPSBDVENMW1sicGNhIl1dKVssIDE6NTBdKQoKZm9yIChpIGluIDE6MTApewogIENUQ0wgPC0gU2V0SWRlbnQoQ1RDTCwgdmFsdWUgPSBwYXN0ZSgiU0NUX3Nubl9yZXMuIixpKjAuMDUsIHNlcCA9ICIiKSkKICBDbHVzdGVycyA9IElkZW50cyhDVENMKQogIHNpbCA8LSBzaWxob3VldHRlKHggPSBhcy5udW1lcmljKHggPSBhcy5mYWN0b3IoeCA9IENsdXN0ZXJzKSksIGRpc3QgPSBkaXN0Lm1hdHJpeCkKICBTaWxTY29yZXNbaSwgMV0gPC0gaSowLjA1CiAgU2lsU2NvcmVzW2ksIDJdIDwtIG1lYW4oc2lsWywzXSkKfQoKIyBQbG90IFNpbGhvdWV0dGUgc2NvcmVzIGFzIGEgZnVuY3Rpb24gb2YgbGVpZGVuIHJlc29sdXRpb24KU2lsU2NvcmVzJFJlcyA8LSBhcy5udW1lcmljKFNpbFNjb3JlcyRSZXMpClNpbFNjb3JlcyRBdmdTaWxXaWR0aCA8LSBhcy5udW1lcmljKFNpbFNjb3JlcyRBdmdTaWxXaWR0aCkKU2lsUGxvdCA8LSBnZ3Bsb3QoU2lsU2NvcmVzLCBhZXMoeCA9IFJlcywgeSA9IEF2Z1NpbFdpZHRoKSkgKyAKICBnZW9tX2xpbmUoc2l6ZSA9IDEpICsKICBnZW9tX3BvaW50KHNpemUgPSAzKSArCiAgc2NhbGVfeV9jb250aW51b3VzKG5hbWUgPSAiQXZnU2lsV2lkdGgiLCBicmVha3MgPSBzZXEoMCwgbWF4KFNpbFNjb3JlcyRBdmdTaWxXaWR0aCksIGJ5ID0gMC4wNSkpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobmFtZSA9ICJMZWlkZW4gUmVzb2x1dGlvbiIsIGJyZWFrcyA9IHNlcSgwLCAwLjUsIGJ5ID0gMC4wNSkpICsKICBnZ3RpdGxlKCJTaWxob3VldHRlIFBsb3QiKSArCiAgdGhlbWVfYncoKQpTaWxQbG90CgpgYGAKCkJhc2VkIG9uIHNpbGhvdWV0dGUgc2NvcmluZywgYSByZXNvbHV0aW9uIG9mIDAuMDUgaXMgbW9zdCBvcHRpbWFsLiBXZSBjYW4gYWxzbyBldmFsdWF0ZSB0aGlzIGJhc2VkIG9uIHRoZSBoZWF0bWFwLCBsb29raW5nIGF0IHdoaWNoIHJlc29sdXRpb24gcHJvZHVjZXMgdGhlIGhpZ2hlc3QgbnVtYmVyIG9mIHRyYW5zY3JpcHRpb25hbGx5IGRpc3RpbmN0IGNsdXN0ZXJzLCBob3dldmVyIEkgZmluZCB0aGF0IHVzaW5nIGEgc3RhdGlzdGljYWwgYXBwcm9hY2ggaXMgYmVzdC4KCk5vdGUgdGhhdCB0aGUgYWJvdmUgYW5hbHlzaXMgY2FuIGFsc28gYmUgcGVyZm9ybWVkIGFjcm9zcyB2YXJ5aW5nIFBDcyAocnVuVU1BUCwgRmluZE5laWdoYm9ycykgYW5kIGsucGFyYW0gKEZpbmROZWlnaGJvcnMpIGluIGFkZGl0aW9uIHRvIHJlc29sdXRpb24gKEZpbmRDbHVzdGVycykuIFNvIGZvciBpbnN0YW5jZSwgaWYgSSBldmFsdWF0ZWQgY2x1c3RlcmluZyBhY3Jvc3MgMyBQQ3MsIDMgay5wYXJhbSwgYW5kIDEwIHJlc29sdXRpb25zIEkgd291bGQgZ2VuZXJhdGUgYW5kIGV2YWx1YXRlIDkwIGNsdXN0ZXJpbmcgaXRlcmF0aW9ucy4gSW4gbXkgZXhwZXJpZW5jZSBJIGZpbmQgdGhhdCBQQ3MgYW5kIGsucGFyYW0gZG8gbm90IGhhdmUgYSBiaWcgaW1wYWN0IG9uIHRoZSBmaW5hbCBkYXRhc2V0LCBidXQgdHVuaW5nIHRoZXNlIG9yIG90aGVyIHBhcmFtZXRlcnMgc2hvdWxkIGJlIGNvbnNpZGVyZWQgZGVwZW5kaW5nIG9uIHRoZSBkYXRhc2V0IGFuZCB0aGUgbmVlZHMgb2YgdGhlIHByb2plY3QuIFRvIGRvIHRoaXMgd2UgY2FuIHVzZSB0aGUgU25ha2VNYWtlIHBpcGVsaW5lLgoKT3RoZXIgYWx0ZXJuYXRpdmUgd2F5cyBvZiBldmFsdWF0aW5nIG9wdGltYWwgcmVzb2x1dGlvbiBpcyB0byBjYWxjdWxhdGUgYSBqYWNjYXJkIGluZGV4IHVzaW5nIHRoZSBzY2NsdXN0ZXZhbCBwYWNrYWdlLCB3aGljaCBpcyBub3QgZGVtb25zdHJhdGVkIGhlcmUuIAoKIyBDRUxMIENZQ0xFIFNDT1JJTkcKClRoZXJlIGRvZXMgbm90IGFwcGVhciB0byBiZWEgbnkgcGFydGljdWxhciBjbHVzdGVyIGRlZmluZWQgYnkgcHJvbGlmZXJhdGluZyBjZWxscy4KCmBgYHtyLCBldmFsID0gRkFMU0V9CiMgQSBsaXN0IG9mIGNlbGwgY3ljbGUgbWFya2VycywgZnJvbSBUaXJvc2ggZXQgYWwsIDIwMTUsIGlzIGxvYWRlZCB3aXRoIFNldXJhdC4gIFdlIGNhbgojIHNlZ3JlZ2F0ZSB0aGlzIGxpc3QgaW50byBtYXJrZXJzIG9mIEcyL00gcGhhc2UgYW5kIG1hcmtlcnMgb2YgUyBwaGFzZQpzLmdlbmVzIDwtIGNjLmdlbmVzJHMuZ2VuZXMKZzJtLmdlbmVzIDwtIGNjLmdlbmVzJGcybS5nZW5lCkNUQ0wgPC0gQ2VsbEN5Y2xlU2NvcmluZyhDVENMLCBzLmZlYXR1cmVzID0gcy5nZW5lcywgZzJtLmZlYXR1cmVzID0gZzJtLmdlbmVzLCBzZXQuaWRlbnQgPSBUUlVFKQpEaW1QbG90KENUQ0wsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgZ3JvdXAuYnkgPSAiUGhhc2UiLCBwdC5zaXplID0gMC4yNSkKRmVhdHVyZVBsb3QoQ1RDTCwgcmVkdWN0aW9uID0gInVtYXAiLCBmZWF0dXJlID0gIkcyTS5TY29yZSIsIHB0LnNpemUgPSAwLjI1KQpGZWF0dXJlUGxvdChDVENMLCByZWR1Y3Rpb24gPSAidW1hcCIsIGZlYXR1cmUgPSAiUy5TY29yZSIsIHB0LnNpemUgPSAwLjI1KQpgYGAKCiMgU2luZ2xlUiBpZGVudGlmaWNhdGlvbiBvZiBjZWxsIHR5cGVzCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpTaW5nbGVSX1JlZiA8LSBsaXN0KCkKQ1RDTC5QcmVkaWN0IDwtIGxpc3QoKQpTaW5nbGVSX1JlZltbIkhQQ0FfUmVmIl1dIDwtIEh1bWFuUHJpbWFyeUNlbGxBdGxhc0RhdGEoKQpTaW5nbGVSX1JlZltbIkRJQ0VfUmVmIl1dIDwtIERhdGFiYXNlSW1tdW5lQ2VsbEV4cHJlc3Npb25EYXRhKCkKU2luZ2xlUl9SZWZbWyJFTkNPREVfUmVmIl1dIDwtIEJsdWVwcmludEVuY29kZURhdGEoKQpyZWYgPC0gYygiSFBDQV9SZWYiLCAiRElDRV9SZWYiLCAiRU5DT0RFX1JlZiIpCgpDVENMIDwtIFNldElkZW50KENUQ0wsIHZhbHVlID0gIlNDVF9zbm5fcmVzLjAuMDUiKQoKZm9yIChpIGluIDE6bGVuZ3RoKHJlZikpIHsKICBDVENMLlByZWRpY3RbW3JlZltpXV1dIDwtIFNpbmdsZVIodGVzdCA9IENUQ0xbWyJTQ1QiXV1AZGF0YSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVmID0gU2luZ2xlUl9SZWZbW3JlZltpXV1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXJzID0gQ1RDTFtbXV0kU0NUX3Nubl9yZXMuMC4wNSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBTaW5nbGVSX1JlZltbcmVmW2ldXV0kbGFiZWwuZmluZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhc3NheS50eXBlLnJlZiA9ICJsb2djb3VudHMiKQogIAogICMgVGhlIGJlbG93IHVzZXMgdGhlIGdyaWQgYW5kIGdyaWRFeHRyYSBwYWNrYWdlcyB0byBhZGQgYSB0aXRsZSB0byBlYWNoIGhlYXRtYXAgaW4gb3JkZXIgdG8gZGlzdGluZ3Vpc2ggdGhlbS4gCiAgIyBUaGUgdGl0bGUgaXMgbm90IGlkZWFsbHkgcG9zaXRpb25lZCBidXQgZG9lcyB0aGUgam9iLCBzbyBpcyBrZXB0IHRoaXMgd2F5LgogIG15X3RpdGxlIDwtIHRleHRHcm9iKHJlZltpXSwgZ3AgPSBncGFyKGZvbnRzaXplID0gMTIsIGZvbnRmYWNlID0gImJvbGQiKSkKICBwbG90IDwtIHBsb3RTY29yZUhlYXRtYXAoQ1RDTC5QcmVkaWN0W1tyZWZbaV1dXSwgb3JkZXIuYnkgPSAiY2x1c3RlcnMiLCBjbHVzdGVycyA9IHJvd25hbWVzKENUQ0wuUHJlZGljdFtbcmVmW2ldXV0pLCBjbHVzdGVyX2NvbHMgPSBGQUxTRSwgc2lsZW50ID0gVFJVRSkKICBncmlkLmFycmFuZ2UoZ3JvYnMgPSBsaXN0KG15X3RpdGxlLCBwbG90W1s0XV0pLCBoZWlnaHRzID0gYygwLjEsIDEpKQogIAogICMgRXhwb3J0IHNjb3JlcyBpbiAuY3N2IGZvcm1hdAogIHdyaXRlLmNzdihDVENMLlByZWRpY3RbW3JlZltpXV1dLCBwYXN0ZSgiL2hvbWUvYm9iYnkvUlN0dWRpby9DVENMX0ltbXVuZVByb2ZpbGluZy9TaW5nbGVSLyIsIHJlZltpXSwgIl9zY29yZS5jc3YiLCBzZXAgPSAiIikpCn0KCiMgQWRkIGluIGxhYmVscyB0byBzZXVyYXQgb2JqZWN0CkNUQ0xAbWV0YS5kYXRhJFNpbmdsZVJfSFBDQV9sZWlkZW5fMC4wNSA8LSAgQ1RDTC5QcmVkaWN0W1siSFBDQV9SZWYiXV0kbGFiZWxzW21hdGNoKENUQ0xbW11dJFNDVF9zbm5fcmVzLjAuMDUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3duYW1lcyhDVENMLlByZWRpY3RbWyJIUENBX1JlZiJdXSkpXQpDVENMQG1ldGEuZGF0YSRTaW5nbGVSX0RJQ0VfbGVpZGVuXzAuMDUgPC0gIENUQ0wuUHJlZGljdFtbIkRJQ0VfUmVmIl1dJGxhYmVsc1ttYXRjaChDVENMW1tdXSRTQ1Rfc25uX3Jlcy4wLjA1LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm93bmFtZXMoQ1RDTC5QcmVkaWN0W1siRElDRV9SZWYiXV0pKV0KQ1RDTEBtZXRhLmRhdGEkU2luZ2xlUl9FTkNPREVfbGVpZGVuXzAuMDUgPC0gIENUQ0wuUHJlZGljdFtbIkVOQ09ERV9SZWYiXV0kbGFiZWxzW21hdGNoKENUQ0xbW11dJFNDVF9zbm5fcmVzLjAuMDUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvd25hbWVzKENUQ0wuUHJlZGljdFtbIkVOQ09ERV9SZWYiXV0pKV0KCnNhdmVSRFMoQ1RDTCwgZmlsZSA9ICIvaG9tZS9ib2JieS9SU3R1ZGlvL0NUQ0xfSW1tdW5lUHJvZmlsaW5nL0NUQ0xfSW1tdW5lUHJvZmlsaW5nLnJkcyIpCgpgYGAKCkdlbmVyYXRlIHBsb3RzCgpgYGB7cn0KRGltUGxvdF9sZWlkZW4wLjA1IDwtIERpbVBsb3QoQ1RDTCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uID0gInVtYXAiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cC5ieSA9ICJTQ1Rfc25uX3Jlcy4wLjA1IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBUUlVFLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwdC5zaXplID0gMC4xKQoKRGltUGxvdF9IUENBIDwtIERpbVBsb3QoQ1RDTCwgCiAgICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9ICJ1bWFwIiwgCiAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwLmJ5ID0gIlNpbmdsZVJfSFBDQV9sZWlkZW5fMC4wNSIsIAogICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IEZBTFNFLCAKICAgICAgICAgICAgICAgICAgICAgICAgcHQuc2l6ZSA9IDAuMSkKRGltUGxvdF9ESUNFIDwtIERpbVBsb3QoQ1RDTCwgCiAgICAgICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9ICJ1bWFwIiwgCiAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwLmJ5ID0gIlNpbmdsZVJfRElDRV9sZWlkZW5fMC4wNSIsIAogICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IEZBTFNFLCAKICAgICAgICAgICAgICAgICAgICAgICAgcHQuc2l6ZSA9IDAuMSkKRGltUGxvdF9FTkNPREUgPC0gRGltUGxvdChDVENMLCAKICAgICAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSAidW1hcCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwLmJ5ID0gIlNpbmdsZVJfRU5DT0RFX2xlaWRlbl8wLjA1IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBGQUxTRSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgcHQuc2l6ZSA9IDAuMSkKCkRpbVBsb3RfbGVpZGVuMC4wNSArIERpbVBsb3RfSFBDQQpEaW1QbG90X2xlaWRlbjAuMDUgKyBEaW1QbG90X0RJQ0UKRGltUGxvdF9sZWlkZW4wLjA1ICsgRGltUGxvdF9FTkNPREUKCmBgYAoKIyBFVkFMVUFURSBUQ1IgQ0xPTkFMSVRZIERBVEEKCmBgYHtyfQpEaW1QbG90KENUQ0wsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgZ3JvdXAuYnkgPSAidGNyIiwgbGFiZWwgPSBGQUxTRSxwdC5zaXplID0gMC4xKQoKQ1RDTEBtZXRhLmRhdGEkdGNyICU+JSB0YWJsZSgpICMgRGlzcGxheSB0b3RhbCBudW1iZXIgb2YgZWFjaCBUQ1IgY2xvbm90eXBlCgojIERpc3BsYXkgZnJlcXVlbmN5IG9mIHRjciBjbG9uZXMgcmVsYXRpdmUgdG8gY2x1c3RlciBJRApDVENMQG1ldGEuZGF0YVssYygiU0NUX3Nubl9yZXMuMC4wNSIsICJ0Y3IiKV0gJT4lIGdyb3VwX2J5KFNDVF9zbm5fcmVzLjAuMDUsIHRjcikgJT4lIHN1bW1hcmlzZShuID0gbigpKSAlPiUKICBtdXRhdGUoZnJlcSA9IG4gLyBzdW0obikpCgpgYGAKCkp1c3QgdmlzdWFsbHkgYmFzZWQgb24gdGhlIFVNQVAgd2UgY2FuIHNlZSB0aGF0IHRoZSBtb3N0IGRvbWluYW50IGNsb25lICMgMSBpcyBtb3N0bHkgcHJlc2VudCBpbiBjbHVzdGVyIDEuIFRoaXMgaXMgYWxzbyBiYWNrZWQgdXAgaW4gdGhlIHJlbGF0aXZlIGZyZXF1ZW5jeSB0YWJsZSwgaW4gd2hpY2ggNzQuNCUgKDM2NDcpIG9mIGNsdXN0ZXIgMSBjZWxscyBhcmUgaWRlbnRpZmllZCBhcyBUQ1IgQ2xvbmUgMS4gSW4gY2x1c3RlciAyLCB0aGUgbW9zdCBmcmVxdWVudCBjbG9uZSBpZGVudGlmaWVkIGlzIGFsc28gY2xvbmUgMSBhdCA3LjMlICg0KS4gRm9yIGNsdXN0ZXIgMywgdGhlIG1vc3QgZnJlcXVlbnQgY2xvbmUgaWRlbnRpZmllZCBpcyBhbHNvIGNsb25lIDEgYXQgMTEuMiUgKDM3KS4KCkluIGFkZGl0aW9uLCBTaW5nbGVSIGFuYWx5c2lzIGlkZW50aWZpZXMgY2x1c3RlcnMgMSBhbmQgMyBhcyBULWNlbGxzIGNvbnNpc3RlbnRseS4gVXNpbmcgSFBDQSBhbmQgRU5DT0RFIHJlZmVyZW5jZXMsIHRoZXNlIGNlbGxzIGFyZSBpZGVudGlmaWVkIGFzIFRDTSBjZWxscywgdGhlIGtub3duIGNlbGwtb2Ytb3JpZ2luIGZvciBDVENMLiBCYXNlZCBvbiB0aGlzIHJlc3VsdCwgY2x1c3RlciAxIGlzIG1vc3QgbGlrZWx5IG1hbGlnbmFudCBjZWxscyBiYXNlZCBvbiBjZWxsIHR5cGUgY2xhc3NpZmljYXRpb24gYW5kIFRDUiBhbmFseXNpcy4gV2hpbGUgY2x1c3RlciAzIG1heSBhbHNvIGJlIG1hbGlnbmFudCBhcyB3ZWxsLCB0aGUgVENSIGRhdGEgZG9lcyBub3QgbmVjZXNzYXJpbHkgc3VwcG9ydCB0aGlzLgoKRm9yIHRoZSBwdXJwb3NlcyBvZiB0aGlzIGFuYWx5c2lzLCBjbHVzdGVyIDEgd2lsbCBiZSBjb25zaWRlcmVkIHRoZSBkb21pbmFudCB0dW1vciBjbG9uZS4gV2hpbGUgdGhpcyBkb2VzIG9taXQgc29tZSBjbG9uZSAxIGNlbGxzIGlkZW50aWZpZWQgaW4gY2x1c3RlciAyIGFuZCAzLCB0aGVzZSB3aWxsIGJlIGNvbnNpZGVyZWQgYXJ0aWZhY3RzIGFzIHRoZSBudW1iZXIgb2YgdGhlc2UgY2VsbHMgaXMgc21hbGwuIAoKVG8gYWRkIGFuIGFkZGl0aW9uYWwgbGF5ZXIgb2YgdmFsaWRhdGlvbiBmb3IgdHVtb3IgY2VsbCBpZGVudGl0eSwgd2UgY2FuIHBlcmZvcm0gY29weSBudW1iZXIgYWx0ZXJhdGlvbiAoQ05BKSBhbmFseXNpcyB1c2luZyBpbmZlckNOViBvciBudW1iYXQuIEJvdGggb2YgdGhlc2UgbWV0aG9kcyByZXF1aXJlIGEgcmVmZXJlbmNlIHdpdGhpbiB0aGUgZGF0YXNldC4gRm9yIHRoaXMgZGF0YXNldCwgb25seSBjbHVzdGVyIDMgY29udGFpbnMgVC1jZWxscyB0aGF0IGFyZSBjYW5kaWRhdGVzIGZvciBub3JtYWwuIEhvd2V2ZXIsIGFzIHRoZXkgY2x1c3RlciBzbyBjbG9zZWx5IHRvIHRoZSBtYWxpZ25hbnQgY2x1c3RlciAxLCB0aGVyZSBhcmUgc29tZSByZWFzb25hYmxlIGNvbmNlcm5zIGFib3V0IHdoZXRoZXIgdGhleSBhcmUgbWFsaWduYW50IG9yIG5vdC4gV2hpbGUgaXQgaXMgZW50aXJlbHkgcG9zc2libGUgZm9yIG1hbGlnbmFudCBjZWxscyB0byBjbG9zZWx5IG1pbWljIHRoZSBnZW5lIGV4cHJlc3Npb24gcHJvZmlsZSBvZiBub3JtYWwgY291bnRlcnBhcnRzLCBpbiB0aGlzIGNhc2Ugd2Uga25vdyB0aGF0IFNlemFyeSBjZWxscyBhcmUgaGlnaGx5IGF0eXBpY2FsLiBJbiB0aGlzIGNvbnRleHQsIEkgd291bGQgbm9ybWFsbHkgc3VnZ2VzdCBpbnRlZ3JhdGluZyBub3JtYWwgY2VsbHMgZnJvbSBhbm90aGVyIHNjUk5BLXNlcSBkYXRhc2V0LiBGb3Igc2ltcGxpY2l0eSBzYWtlLCB0aGlzIGlzIG5vdCBkb25lIGhlcmUgYW5kIGEgbnVtYmF0IGFuYWx5c2lzIHVzaW5nIGNsdXN0ZXIgMSBhcyBxdWVyeSBjZWxscyBhbmQgdXNpbmcgY2x1c3RlciAzIGFzIHJlZmVyZW5jZSBpcyBkZW1vbnN0cmF0ZWQgYmVsb3cuIAoKIyBOVU1CQVQgSU5URUdSQVRJT04KCiMgUFJFLVBST0NFU1MgU05QIERBVEEKCmh0dHBzOi8va2hhcmNoZW5rb2xhYi5naXRodWIuaW8vbnVtYmF0L2FydGljbGVzL251bWJhdC5odG1sI3ByZXBhcmluZy1kYXRhCgpQcmVwYXJpbmcgZGF0YSBkb2VzIG5vdCB1c2UgbXVjaCBtZW1vcnkuIEl0IGlzIGJlc3QgdG8gcnVuIHRoaXMgaW4gYSBjb21wdXRlIGludGVuc2l2ZSBpbnN0YW5jZSAoQzVhLCBmb3IgaW5zdGFuY2UpLgoKYGBge2Jhc2ggZXZhbCA9IEZBTFNFfQoKUnNjcmlwdCAvaG9tZS9ib2JieS9udW1iYXQvaW5zdC9iaW4vcGlsZXVwX2FuZF9waGFzZS5SIFwKICAgIC0tbGFiZWwgQ1RDTCBcCiAgICAtLXNhbXBsZXMgQ1RDTCBcCiAgICAtLWJhbXMgL2hvbWUvYm9iYnkvUlN0dWRpby9DVENMX0ltbXVuZVByb2ZpbGluZy9udW1iYXQvcHJlcHJvY2Vzc2luZy9zYW1wbGVfYWxpZ25tZW50cy5iYW0gXAogICAgLS1iYXJjb2RlcyAvaG9tZS9ib2JieS9SU3R1ZGlvL0NUQ0xfSW1tdW5lUHJvZmlsaW5nL251bWJhdC9wcmVwcm9jZXNzaW5nL2JhcmNvZGVzLnRzdiBcCiAgICAtLW91dGRpciAvaG9tZS9ib2JieS9SU3R1ZGlvL0NUQ0xfSW1tdW5lUHJvZmlsaW5nL251bWJhdC9wcmVwcm9jZXNzaW5nLyBcCiAgICAtLWdtYXAgL2hvbWUvYm9iYnkvRWFnbGVfdjIuNC4xL3RhYmxlcy9nZW5ldGljX21hcF9oZzM4X3dpdGhYLnR4dC5neiBcCiAgICAtLXNucHZjZiAvaG9tZS9ib2JieS9udW1iYXRfZGF0YS9nZW5vbWUxSy5waGFzZTMuU05QX0FGNWUyLmNocjF0b1guaGczOC52Y2YgXAogICAgLS1wYW5lbGRpciAvaG9tZS9ib2JieS9udW1iYXRfZGF0YS8xMDAwR19oZzM4IFwKICAgIC0tbmNvcmVzIDgKCmd6aXAgLWRrIC9ob21lL2JvYmJ5L1JTdHVkaW8vQ1RDTF9JbW11bmVQcm9maWxpbmcvbnVtYmF0L3ByZXByb2Nlc3NpbmcvQ1RDTF9hbGxlbGVfY291bnRzLnRzdi5negoKYGBgCgojIFBSRVBBUkUgRVhQUkVTU0lPTiBEQVRBIAoKTnVtYmF0IHRha2VzIGEgZ2VuZSBieSBjZWxsIGludGVnZXIgVU1JIGNvdW50IG1hdHJpeCBhcyBpbnB1dC4gWW91IGNhbiBkaXJlY3RseSB1c2UgcmVzdWx0cyBmcm9tIHVwc3RyZWFtIHRyYW5zY3JpcHRvbWUgcXVhbnRpZmljYXRpb24gcGlwZWxpbmVzIHN1Y2ggYXMgMTB4IENlbGxSYW5nZXIuCgpgYGB7ciBldmFsID0gRkFMU0V9CmxpYnJhcnkobnVtYmF0KQpsaWJyYXJ5KHJlYWRyKQoKQ1RDTCA8LSBTZXRJZGVudChDVENMLCB2YWx1ZSA9ICJTQ1Rfc25uX3Jlcy4wLjA1IikKCiMgQ3JlYXRlIHNldXJhdCBvYmplY3QgZm9yIGNlbGxzIG9mIGludGVyZXN0CkNUQ0xfbnVtYmF0IDwtIHN1YnNldChDVENMLCBpZGVudCA9IDEpCgojIENyZWF0ZSBzZXVyYXQgb2JqZWN0IGZvciByZWZlcmVuY2UgY2VsbHMKQ1RDTF9udW1iYXRfcmVmIDwtIHN1YnNldChDVENMLCBpZGVudCA9IDMpCgojIEV4cG9ydCBjb3VudCBtYXRyaXggZm9yIGNlbGxzIG9mIGludGVyZXN0CmNvdW50X21hdCA8LSBDVENMX251bWJhdFtbIlJOQSJdXUBjb3VudHMKCiMgUHJlcGFyZSByZWZlcmVuY2UgY291bnQgZGF0YS4gSWYgdXNpbmcgY3VzdG9tIHJlZmVyZW5jZSB3aWxsIG5lZWQgdG8gdXNlIGFnZ3JlZ2F0ZV9jb3VudCgpCiMgY2VsbF9hbm5vdCBpcyBhIGRhdGFmcmFtZSB3aXRoIGNvbHVtbnMgImNlbGwiIGFuZCAiZ3JvdXAiCmNvdW50X21hdF9yZWYgPC0gQ1RDTF9udW1iYXRfcmVmW1siUk5BIl1dQGNvdW50cwpjZWxsX2Fubm90IDwtIGRhdGEuZnJhbWUobWF0cml4KG5yb3cgPSBuY29sKENUQ0xfbnVtYmF0X3JlZiksIG5jb2wgPSAwKSkKY2VsbF9hbm5vdCRjZWxsIDwtIENUQ0xfbnVtYmF0X3JlZiAlPiUgY29sbmFtZXMoKQpjZWxsX2Fubm90JGdyb3VwIDwtIENUQ0xfbnVtYmF0X3JlZkBtZXRhLmRhdGEkb3JpZy5pZGVudApyZWZfaW50ZXJuYWwgPC0gYWdncmVnYXRlX2NvdW50cyhjb3VudF9tYXRfcmVmLCBjZWxsX2Fubm90KQpybShjZWxsX2Fubm90KQoKc2F2ZVJEUyhjb3VudF9tYXQsIGZpbGUgPSAiL2hvbWUvYm9iYnkvUlN0dWRpby9DVENMX0ltbXVuZVByb2ZpbGluZy9udW1iYXQvY291bnRfbWF0LnJkcyIpCnNhdmVSRFMoY291bnRfbWF0X3JlZiwgZmlsZSA9ICIvaG9tZS9ib2JieS9SU3R1ZGlvL0NUQ0xfSW1tdW5lUHJvZmlsaW5nL251bWJhdC9jb3VudF9tYXRfcmVmLnJkcyIpCnNhdmVSRFMocmVmX2ludGVybmFsLCBmaWxlID0gIi9ob21lL2JvYmJ5L1JTdHVkaW8vQ1RDTF9JbW11bmVQcm9maWxpbmcvbnVtYmF0L3JlZl9pbnRlcm5hbC5yZHMiKQoKYGBgCgojIFJVTiBOVU1CQVQKCmBgYHtyIGV2YWwgPSBGQUxTRX0KCmxpYnJhcnkobnVtYmF0KQpsaWJyYXJ5KHJlYWRyKQoKY291bnRfbWF0IDwtIHJlYWRSRFMoZmlsZSA9ICIvaG9tZS9ib2JieS9SU3R1ZGlvL0NUQ0xfSW1tdW5lUHJvZmlsaW5nL251bWJhdC9jb3VudF9tYXQucmRzIikKcmVmX2ludGVybmFsIDwtIHJlYWRSRFMoZmlsZSA9ICIvaG9tZS9ib2JieS9SU3R1ZGlvL0NUQ0xfSW1tdW5lUHJvZmlsaW5nL251bWJhdC9yZWZfaW50ZXJuYWwucmRzIikKZGZfYWxsZWxlIDwtIHJlYWRfdHN2KGZpbGUgPSAiL2hvbWUvYm9iYnkvUlN0dWRpby9DVENMX0ltbXVuZVByb2ZpbGluZy9udW1iYXQvcHJlcHJvY2Vzc2luZy9DVENMX2FsbGVsZV9jb3VudHMudHN2IikKCiMgcnVuCm91dCA8LSBydW5fbnVtYmF0KAogICAgY291bnRfbWF0LCAjIGdlbmUgeCBjZWxsIGludGVnZXIgVU1JIGNvdW50IG1hdHJpeCAKICAgIHJlZl9pbnRlcm5hbCwgIyByZWZlcmVuY2UgZXhwcmVzc2lvbiBwcm9maWxlLCBhIGdlbmUgeCBjZWxsIHR5cGUgbm9ybWFsaXplZCBleHByZXNzaW9uIGxldmVsIG1hdHJpeAogICAgZGZfYWxsZWxlLCAjIGFsbGVsZSBkYXRhZnJhbWUgZ2VuZXJhdGVkIGJ5IHBpbGV1cF9hbmRfcGhhc2Ugc2NyaXB0CiAgICBnZW5vbWUgPSAiaGczOCIsCiAgICB0ID0gMWUtNSwKICAgIG5jb3JlcyA9IDgsCiAgICBwbG90ID0gVFJVRSwKICAgIG1heF9lbnRyb3B5ID0gMC41LAogICAgb3V0X2RpciA9ICcvaG9tZS9ib2JieS9SU3R1ZGlvL0NUQ0xfSW1tdW5lUHJvZmlsaW5nL251bWJhdC9vdXQvJwopCgpwcmludChvdXQpCgpgYGAKCkZpbmFsIHJlc3VsdHMgYW5kIGNsb25hbCBhc3NpZ25tZW50cyBpcyB2aXN1YWxpemVkIGJlbG93LiBOb3RlIHRoYXQgY2xvbmUgMSBpcyBkZWZpbmVkIGFzIG5vcm1hbC4gUmVmZXJlbmNlIGNlbGxzIGFyZSBub3QgdmlzdWFsaXplZC4KCmBgYHtyIGV2YWwgPSBGQUxTRX0KIyBWaXN1YWxpemUgbXV0YXRpb25hbCBoaXN0b3J5Cm5iIDwtIE51bWJhdCRuZXcob3V0X2RpciA9ICcvaG9tZS9ib2JieS9SU3R1ZGlvL0NUQ0xfSW1tdW5lUHJvZmlsaW5nL251bWJhdC9vdXQnKQpwbmcoZmlsZW5hbWUgPSAiL2hvbWUvYm9iYnkvUlN0dWRpby9DVENMX0ltbXVuZVByb2ZpbGluZy9udW1iYXQvcGxvdF9tdXRfaGlzdG9yeS5wbmciLCB3aWR0aCA9IDUwMCwgaGVpZ2h0ID0gMTUwKQpwbG90IDwtIG5iJHBsb3RfbXV0X2hpc3RvcnkoKQpwcmludChwbG90KQpkZXYub2ZmKCkKYGBgCgoKYGBge3IgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCIvaG9tZS9ib2JieS9SU3R1ZGlvL0NUQ0xfSW1tdW5lUHJvZmlsaW5nL251bWJhdC9vdXQvYnVsa19jbG9uZXNfZmluYWwucG5nIikKa25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIi9ob21lL2JvYmJ5L1JTdHVkaW8vQ1RDTF9JbW11bmVQcm9maWxpbmcvbnVtYmF0L3Bsb3RfbXV0X2hpc3RvcnkucG5nIikKYGBgCgpOdW1iYXQgaXMgaWRlbnRpZnlpbmcgZGVsZXRpb25zIGluIGNociA0LCA2LCAxMCBhbmQgYW1wIGluIGNociAxMSBhbmQgMTkuIERlbGV0aW9ucyBpbiBjaHIgMTBxIGFyZSByZWN1cnJlbnQgaW4gQ1RDTCAoaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DNDU1MjYxNC8pLCBwcm92aWRpbmcgYWRkaXRpb25hbCBldmlkZW5jZSB0aGF0IGNsdXN0ZXIgMSByZXByZXNlbnRzIG1hbGlnbmFudCBjZWxscy4gTnVtYmF0IGNsb25lIDIgaXMgaWRlbnRpZmllZCBhcyBhIHByZS1jdXJzb3IgY2VsbCBwb3B1bGF0aW9uIHRvIGNsb25lIDMuIEludGVyZXN0aW5nbHksIG1vc3Qgb2YgdGhlIGNvcHkgbnVtYmVyIGV2ZW50cyBpZGVudGlmaWVkIGluIHRoaXMgY2xvbmUgYXJlIHRyYW5zY3JpcHRpb25hbGx5IG5ldXRyYWwsIGltcGx5aW5nIHRoYXQgdGhlc2UgY2VsbHMgYXJlIHBoZW5vdHlwaWNhbGx5ICJub3JtYWwiLiBNb3JlIGludmVzdGlnYXRpb24gd291bGQgbmVlZCB0byBiZSBkb25lIHRvIGNvbmZpcm0gd2hldGhlciB0aGVzZSBhcmUgdHJ1bHkgbWFsaWduYW50IHByZWN1cnNvcnMuIFRoZSBwcmVzZW5jZSBvZiB+MTEzICJub3JtYWwiIGNlbGxzIG1heSByZXByZXNlbnQgbm9ybWFsIGNlbGxzIHRoYXQgY2xvc2VseSByZXNlbWJsZSB0aGUgbWFsaWduYW50IGNlbGxzLCBhbmQgbWF5IGV2ZW4gYmUgY29uc2lkZXJlZCB0aGUgY2VsbCBvZiBvcmlnaW4gZm9yIHRoZSB0dW1vciAoYWx0aG91Z2ggbW9yZSBldmlkZW5jZSBpcyBuZWVkZWQgdG8gc3VwcG9ydCB0aGlzKS4gCgojIElNUE9SVCBOVU1CQVQgUkVTVUxUUyBUTyBTRVVSQVQgT0JKRUNUCgpgYGB7ciBldmFsID0gRkFMU0V9CiMgbmIgPC0gTnVtYmF0JG5ldyhvdXRfZGlyID0gJy9ob21lL2JvYmJ5L1JTdHVkaW8vQ1RDTF9JbW11bmVQcm9maWxpbmcvbnVtYmF0L291dCcpCgpuYl9jbG9uZXBvc3QgPC0gbmIkY2xvbmVfcG9zdAoKbnVtYmF0X21ldGEgPC0gZGF0YS5mcmFtZShtYXRyaXgobmNvbCA9IDIsIG5yb3cgPSBDVENMICU+JSBuY29sKCkpKQpjb2xuYW1lcyhudW1iYXRfbWV0YSkgPC0gYygiY2VsbCIsICJjbG9uZV9vcHQiKQpudW1iYXRfbWV0YSRjZWxsIDwtIENUQ0wgJT4lIGNvbG5hbWVzKCkKbnVtYmF0X21ldGEkY2xvbmVfb3B0IDwtICJOQSIgIyBBbGwgY2VsbHMgbm90IGNvbnRhaW5lZCB3aXRoaW4gdGhlIHNldXJhdCBvYmplY3Qgd2lsbCBiZSBsYWJlbGxlZCAiTkEiIGhlcmUKbnVtYmF0X21ldGEkY2xvbmVfb3B0W21hdGNoKG5iX2Nsb25lcG9zdCRjZWxsLCBudW1iYXRfbWV0YSRjZWxsKV0gPC0gbmJfY2xvbmVwb3N0JGNsb25lX29wdCAjIEFkZCBpbiBudW1iYXQgY2xvbmUgbGFiZWwKcm93Lm5hbWVzKG51bWJhdF9tZXRhKSA8LSBudW1iYXRfbWV0YSRjZWxsCm51bWJhdF9tZXRhJGNlbGwgPC0gTlVMTAoKQ1RDTEBtZXRhLmRhdGEkbnVtYmF0X2Nsb25lcG9zdCA8LSBudW1iYXRfbWV0YSRjbG9uZV9vcHQKCmBgYAoKYGBge3IgZXZhbCA9IEZBTFNFfQpEaW1QbG90KENUQ0wsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgZ3JvdXAuYnkgPSAibnVtYmF0X2Nsb25lcG9zdCIsIGNvbHMgPSBjKCIjRTQxQTFDIiwiIzM3N0VCOCIsIiM0REFGNEEiLCAiZ3JheSIpLCBvcmRlciA9IGMoIk5BIiwgIjMiLCAiMiIsICIxIiksIHB0LnNpemUgPSAwLjA1LCByYXN0ZXIgPSBGQUxTRSkKYGBgCgojIElNUE9SVCBTSU5HTEUgQ0VMTCBDTlYgUE9TVEVSSU9SUyBUTyBTRVVSQVQgT0JKRUNUCgpDTlYgcG9zdGVyaW9yIGRvZXMgbm90IHJlcHJlc2VudCBhICJkZWdyZWUiIG9mIENOViBidXQgcmF0aGVyIHRoZSBwcm9iYWJpbGl0eSBvZiB0aGF0IGV2ZW50IG9jY3VycmluZyBpbiBhIHNpbmdsZSBjZWxsLiBIZXJlLCB3ZSBpbXBvcnQgdGhlc2UgQ05WIHBvc3RlcmlvcnMgdG8gdGhlIHNldXJhdCBvYmplY3QgdG8gdmlzdWFsaXplIHRoaXMgdXNpbmcgc3RhbmRhcmQgc2V1cmF0IHRvb2xzIChGZWF0dXJlUGxvdCwgRG90UGxvdCwgVmxuUGxvdCwgZXRjKS4KCmBgYHtyIGV2YWwgPSBGQUxTRX0KCm5iX2pvaW50X3Bvc3QgPC0gbmIkam9pbnRfcG9zdAoKbXV0cyAgPC0gIG5iJGpvaW50X3Bvc3QgJT4lIGRpc3RpbmN0KHNlZykKbXV0cyA8LSBtdXRzJHNlZwpjbnZfdHlwZSA8LSAgbmIkam9pbnRfcG9zdCAlPiUgZGlzdGluY3Qoc2VnLCBjbnZfc3RhdGUpICU+JSB7c2V0TmFtZXMoLiRjbnZfc3RhdGUsIC4kc2VnKX0KCmNudl9wb3N0X21ldGEgPC0gZGF0YS5mcmFtZShtYXRyaXgoMCwgbmNvbCA9IGxlbmd0aChtdXRzKSwgbnJvdyA9IENUQ0wgJT4lIG5jb2woKSkpCnJvd25hbWVzKGNudl9wb3N0X21ldGEpIDwtIENUQ0wgJT4lIGNvbG5hbWVzKCkKY252X25hbWUgPC0gcGFzdGUobXV0cywgY252X3R5cGUsICJwIiwgImNudiIsIHNlcCA9ICJfIikKY29sbmFtZXMoY252X3Bvc3RfbWV0YSkgPC0gY252X25hbWUKCmZvciAoaSBpbiAxOmxlbmd0aChtdXRzKSkgewogIG5iX2pvaW50X3Bvc3RfdGVtcCA8LSBzdWJzZXQobmJfam9pbnRfcG9zdCwgc2VnID09IG11dHNbaV0gJiBjbnZfc3RhdGUgPT0gY252X3R5cGVbaV0pCiAgY252X3Bvc3RfbWV0YVttYXRjaChuYl9qb2ludF9wb3N0X3RlbXAkY2VsbCwgcm93bmFtZXMoY252X3Bvc3RfbWV0YSkpLGldIDwtIG5iX2pvaW50X3Bvc3RfdGVtcCRwX2NudgogIHJtKG5iX2pvaW50X3Bvc3RfdGVtcCkKfQoKQ1RDTEBtZXRhLmRhdGEgPC0gY2JpbmQoQ1RDTEBtZXRhLmRhdGEsIGNudl9wb3N0X21ldGEpICMgQWRkIGludG8gU2V1cmF0IG1ldGEgZGF0YQoKcm0oY252X3Bvc3RfbWV0YSkKcm0obmJfam9pbnRfcG9zdCkKCiMgVmlzdWFsaXplIHBvc3RlcmlvcnMgdXNpbmcgVmxuUGxvdCBhbmQgc2F2ZSBwbG90cwpmb3IgKGkgaW4gMTpsZW5ndGgobXV0cykpIHsKICBzZXR3ZCgiL2hvbWUvYm9iYnkvUlN0dWRpby9DVENMX0ltbXVuZVByb2ZpbGluZy9udW1iYXQvc2V1cmF0L1ZsblBsb3QiKQogIHBuZyhmaWxlbmFtZSA9IHBhc3RlKGNudl9uYW1lW2ldLCAiLnBuZyIsIHNlcCA9ICIiKSwgd2lkdGggPSA5MDAsIGhlaWdodCA9IDUwMCkKICBwbG90IDwtIFZsblBsb3QoQ1RDTCwgZmVhdHVyZXMgPSBjbnZfbmFtZVtpXSwgZ3JvdXAuYnkgPSAibnVtYmF0X2Nsb25lcG9zdCIsIHJhc3RlciA9IEZBTFNFKQogIHByaW50KHBsb3QpCiAgZGV2Lm9mZigpCiAgcm0ocGxvdCkKICBzZXR3ZCgiL2hvbWUvYm9iYnkiKQp9CgojIERvdCBwbG90IHJlcHJlc2VudGF0aW9uIGlzIG5vdCB2ZXJ5IHVzZWZ1bCBzaW5jZSBldmVyeSBjZWxsIGluIHRoZSBncm91cCB3aWxsICJleHByZXNzIiB0aGUgQ05WLCBhcyBudW1iYXQgd2lsbCBhc3NpZ24gYSBzY29yZSB0byBhbGwgY2VsbHMKIyBDb2RlIGlzIGxlZnQgaGVyZSBtb3N0bHkgZm9yIGRlbW9uc3RyYXRpb24KRG90UGxvdChDVENMLCAKICAgICAgICBmZWF0dXJlcyA9IENUQ0xAbWV0YS5kYXRhWywyOTooMjkrbGVuZ3RoKG11dHMpLTEpXSAlPiUgY29sbmFtZXMoKSwKICAgICAgICBncm91cC5ieSA9ICJudW1iYXRfY2xvbmVwb3N0IiwKICAgICAgICBjb2wubWluID0gMCwKICAgICAgICBjb2wubWF4ID0gMSkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCB2anVzdCA9IDEsIGhqdXN0PTEpKQoKYGBgCgpBZnRlciBpbXBvcnRpbmcgbnVtYmF0IGNsb25lIElEcyB0byB0aGUgc2V1cmF0IG9iamVjdCwgd2UgY2FuIHBlcmZvcm0gYW4gaW50ZWdyYXRpdmUgYW5hbHlzaXMgdXNpbmcgbnVtYmF0IChDTlYpIGFuZCBUQ1IKCmBgYHtyfQoKIyBHZW5lcmF0ZSBUQ1IgKyBudW1iYXQgY2xvbmVwb3N0IGNvbWJpbmVkIGRpbXBsb3QKcGxvdDEgPC0gRGltUGxvdChDVENMLCByZWR1Y3Rpb24gPSAidW1hcCIsIGdyb3VwLmJ5ID0gInRjciIsIGxhYmVsID0gRkFMU0UscHQuc2l6ZSA9IDAuMSkKcGxvdDIgPC0gRGltUGxvdChDVENMLCByZWR1Y3Rpb24gPSAidW1hcCIsIGdyb3VwLmJ5ID0gIm51bWJhdF9jbG9uZXBvc3QiLCBjb2xzID0gYygiI0U0MUExQyIsIiMzNzdFQjgiLCIjNERBRjRBIiwgImdyYXkiKSwgb3JkZXIgPSBjKCJOQSIsICIzIiwgIjIiLCAiMSIpLCBwdC5zaXplID0gMC4wNSwgcmFzdGVyID0gRkFMU0UpCnBuZyhmaWxlbmFtZSA9ICIvaG9tZS9ib2JieS9SU3R1ZGlvL0NUQ0xfSW1tdW5lUHJvZmlsaW5nL3Rjcl9udW1iYXRjbG9uZV9kaW1wbG90LnBuZyIsIHdpZHRoID0gMTAwMCwgaGVpZ2h0ID0gNDUwKQpwbG90MSArIHBsb3QyCmRldi5vZmYoKQpybShwbG90MSkKcm0ocGxvdDIpCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCIvaG9tZS9ib2JieS9SU3R1ZGlvL0NUQ0xfSW1tdW5lUHJvZmlsaW5nL3Rjcl9udW1iYXRjbG9uZV9kaW1wbG90LnBuZyIpCgojIENhbGN1bGF0ZSBmcmVxdWVuY3kgb2YgdGNyIGNsb25lIHJlbGF0aXZlIHRvIG51bWJhdCBjbG9uZQpUQ1JfZnJlcSA8LSBDVENMQG1ldGEuZGF0YVssIGMoIm51bWJhdF9jbG9uZXBvc3QiLCJ0Y3IiKV0gJT4lIHRhYmxlKCkgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgc3Vic2V0KEZyZXEgPiAxKSAjIEV4Y2x1ZGUgY2xvbmVzIHdpdGggb25seSAxIGNlbGwKcGFsZXR0ZSA8LSBjb2xvclJhbXBQYWxldHRlKGNvbG9ycyA9IGJyZXdlci5wYWwoMTIsICJQYWlyZWQiKSkoVENSX2ZyZXEkdGNyICU+JSB1bmlxdWUoKSAlPiUgbGVuZ3RoKCkgLSAxKQpwYWxldHRlIDwtIGMocGFsZXR0ZSwgImdyYXkiKQpnZ3Bsb3QoVENSX2ZyZXEsIGFlcyh4ID0gbnVtYmF0X2Nsb25lcG9zdCwgeSA9IEZyZXEsIGZpbGwgPSB0Y3IpKSArCiAgZ2VvbV9jb2woY29sb3VyID0gImJsYWNrIiwgcG9zaXRpb24gPSAiZmlsbCIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcGFsZXR0ZSkgKwogIGdndGl0bGUoIlRDUiBjbG9uZSBmcmVxdWVuY3kgYnkgbnVtYmF0IGNsb25lIikKCiMgVmlldyByZWxhdGl2ZSBmcmVxdWVuY3kgYXMgYSB0YWJsZQpUQ1JfZnJlcSA8LSBDVENMQG1ldGEuZGF0YVssIGMoIm51bWJhdF9jbG9uZXBvc3QiLCJ0Y3IiKV0gJT4lIHRhYmxlKCkgJT4lIGFzLmRhdGEuZnJhbWUoKQpUQ1JfZnJlcSAlPiUKICBncm91cF9ieShudW1iYXRfY2xvbmVwb3N0KSAlPiUKICBzdWJzZXQoRnJlcSA+IDApICU+JSAKICBtdXRhdGUocGVyY2VudCA9IEZyZXEvc3VtKEZyZXEpKSAlPiUgCiAgYXJyYW5nZShudW1iYXRfY2xvbmVwb3N0LCAtcGVyY2VudCkKCmBgYAoKTG9va2luZyBhdCB0aGUgYmFyIGNoYXJ0IHdlIHNlZSB0aGF0IG51bWJhdCBjbG9uZSAxICh3aGljaCBpcyBkZWZpbmVkIGFzIG5vcm1hbCksIGFjdHVhbGx5IGNvbnRhaW5zIGEgaGlnaCBwZXJjZW50YWdlIG9mIHRjciBjbG9uZSAxLiBIb3dldmVyLCBnaXZlbiB0aGF0IHRoZSBudW1iZXIgb2YgY2VsbHMgaW4gbnVtYmF0IGNsb25lIDEgaXMgcmF0aGVyIHNtYWxsLCB0aGlzIG1heSBzaW1wbHkgYmUgYW4gYXJ0aWZhY3QgYW5kIHdlIGFsc28gc2hvdWxkIGtlZXAgaW4gbWluZCB0aGF0IHRoZSBtYWpvcml0eSBvZiBjZWxscyBhcmUgc3RpbGwgbm90IHRjciBsYWJlbGxlZC4KCk90aGVyd2lzZSwgd2Ugc2VlIHRoYXQgbnVtYmF0IGNsb25lIDIgYW5kIDMgYXJlIGJvdGggY29tcHJpc2VkIG9mIH44MCUgVENSIGNsb25lIDEsIGFnYWluIHByb3ZpZGluZyBldmlkZW5jZSB0aGF0IHRoZXNlIGFyZSwgaW5kZWVkLCB0dW1vciBjZWxscy4gSW50ZXJlc3RpbmdseSwgdGhlIFRDUiBkYXRhIGRvZXMgbm90IHN1Z2dlc3QgdGhlIGV4aXN0ZW5jZSBvZiB0d28gY2xvbmVzIGFzIG51bWJhdCBpcyBpbXBseWluZy4KCiMgTU9OT0NMRSBQU0VVRE9USU1FIEFOQUxZU0lTCgpHaXZlbiB0aGF0IG51bWJhdCBpcyBzdWdnZXN0aW5nIHRoZSBleGlzdGVuY2Ugb2YgdHdvIHR1bW9yIGNsb25lcyBhbmQgaGFzIGluZmVycmVkIGFuIGV2b2x1dGlvbmFyeSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgdHdvIGNsb25lcywgd2UgY2FuIGFsc28gdXNlIHBzZXVkb3RpbWUgYW5hbHlzaXMgdG8gYWRkcmVzcyB0aGlzLiBIZXJlLCB3ZSB1c2UgbW9ub2NsZSAoaHR0cHM6Ly9jb2xlLXRyYXBuZWxsLWxhYi5naXRodWIuaW8vbW9ub2NsZTMvZG9jcy9pbnRyb2R1Y3Rpb24vKS4gCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQojIEluc3RhbGwgbW9ub2NsZTMgdGhyb3VnaCB0aGUgY29sZS10cmFwbmVsbC1sYWIgR2l0SHViCiMgbGlicmFyeSgiZGV2dG9vbHMiKQojIGRldnRvb2xzOjppbnN0YWxsX2dpdGh1YignY29sZS10cmFwbmVsbC1sYWIvbGVpZGVuYmFzZScpCiMgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCdjb2xlLXRyYXBuZWxsLWxhYi9tb25vY2xlMycpCmxpYnJhcnkobW9ub2NsZTMpCgpleHByZXNzaW9uX21hdHJpeCA8LSBDVENMW1siUk5BIl1dQGNvdW50cwpjZWxsX21ldGFkYXRhIDwtIENUQ0xAbWV0YS5kYXRhWyxjKCJ0Y3IiLCAibnVtYmF0X2Nsb25lcG9zdCIsICJTQ1Rfc25uX3Jlcy4wLjA1IildCmdlbmVfbWV0YWRhdGEgPC0gZGF0YS5mcmFtZShyb3cubmFtZXMgPSByb3duYW1lcyhleHByZXNzaW9uX21hdHJpeCksIGdlbmVfc2hvcnRfbmFtZSA9IHJvd25hbWVzKGV4cHJlc3Npb25fbWF0cml4KSkgIyBUaGlzIGlzIHJlcXVpcmVkIGZvciBuZXdfY2VsbF9kYXRhX3NldCgpIGZ1bmN0aW9uCgpDVENMX21vbm9jbGUgPC0gbmV3X2NlbGxfZGF0YV9zZXQoZXhwcmVzc2lvbl9tYXRyaXgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZWxsX21ldGFkYXRhID0gY2VsbF9tZXRhZGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlbmVfbWV0YWRhdGEgPSBnZW5lX21ldGFkYXRhKQoKIyBQcmUtcHJvY2VzcyB0aGUgZGF0YS4gTm90ZSB0aGF0IGEgYmF0Y2ggZWZmZWN0IGNvcnJlY3Rpb24gc3RlcCBpcyBub3QgaW5jbHVkZWQgaGVyZQpDVENMX21vbm9jbGUgPC0gcHJlcHJvY2Vzc19jZHMoQ1RDTF9tb25vY2xlLCBudW1fZGltID0gMjApCkNUQ0xfbW9ub2NsZSA8LSByZWR1Y2VfZGltZW5zaW9uKENUQ0xfbW9ub2NsZSwgcmVkdWN0aW9uX21ldGhvZCA9ICJVTUFQIiwgcHJlcHJvY2Vzc19tZXRob2QgPSAiUENBIikKQ1RDTF9tb25vY2xlIDwtIGNsdXN0ZXJfY2VsbHMoQ1RDTF9tb25vY2xlLCBjbHVzdGVyX21ldGhvZCA9ICdsb3V2YWluJykKcGxvdF9jZWxscyhDVENMX21vbm9jbGUsIGNvbG9yX2NlbGxzX2J5ID0gInBhcnRpdGlvbiIpCgpDVENMX21vbm9jbGUgPC0gbGVhcm5fZ3JhcGgoQ1RDTF9tb25vY2xlKQoKcGxvdF9jZWxscyhDVENMX21vbm9jbGUsCiAgICAgICAgICAgY29sb3JfY2VsbHNfYnkgPSAicGFydGl0aW9uIiwKICAgICAgICAgICBsYWJlbF9ncm91cHNfYnlfY2x1c3Rlcj1GQUxTRSwKICAgICAgICAgICBsYWJlbF9sZWF2ZXM9RkFMU0UsCiAgICAgICAgICAgbGFiZWxfYnJhbmNoX3BvaW50cz1GQUxTRSkKCnBsb3RfY2VsbHMoQ1RDTF9tb25vY2xlLAogICAgICAgICAgIGNvbG9yX2NlbGxzX2J5ID0gIm51bWJhdF9jbG9uZXBvc3QiLAogICAgICAgICAgIGxhYmVsX2dyb3Vwc19ieV9jbHVzdGVyPUZBTFNFLAogICAgICAgICAgIGxhYmVsX2xlYXZlcz1GQUxTRSwKICAgICAgICAgICBsYWJlbF9icmFuY2hfcG9pbnRzPUZBTFNFKQoKcGxvdF9jZWxscyhDVENMX21vbm9jbGUsCiAgICAgICAgICAgY29sb3JfY2VsbHNfYnkgPSAiU0NUX3Nubl9yZXMuMC4wNSIsCiAgICAgICAgICAgbGFiZWxfZ3JvdXBzX2J5X2NsdXN0ZXI9RkFMU0UsCiAgICAgICAgICAgbGFiZWxfbGVhdmVzPUZBTFNFLAogICAgICAgICAgIGxhYmVsX2JyYW5jaF9wb2ludHM9RkFMU0UpCgpDVENMX21vbm9jbGUgPC0gb3JkZXJfY2VsbHMoQ1RDTF9tb25vY2xlKQoKcGxvdF9jZWxscyhDVENMX21vbm9jbGUsCiAgICAgICAgICAgY29sb3JfY2VsbHNfYnkgPSAicHNldWRvdGltZSIsCiAgICAgICAgICAgbGFiZWxfY2VsbF9ncm91cHM9RkFMU0UsCiAgICAgICAgICAgbGFiZWxfbGVhdmVzPUZBTFNFLAogICAgICAgICAgIGxhYmVsX2JyYW5jaF9wb2ludHM9RkFMU0UsCiAgICAgICAgICAgZ3JhcGhfbGFiZWxfc2l6ZT0xLjUpCgpzYXZlUkRTKENUQ0xfbW9ub2NsZSwgZmlsZSA9ICIvaG9tZS9ib2JieS9SU3R1ZGlvL0NUQ0xfSW1tdW5lUHJvZmlsaW5nL21vbm9jbGUvQ1RDTF9tb25vY2xlLnJkcyIpCgojIEV4cG9ydCBwc2V1ZG90aW1lIGNhbGxzIHRvIHRoZSBTZXVyYXQgb2JqZWN0CnBzZXVkb3RpbWUgPC0gYXMuZGF0YS5mcmFtZShwc2V1ZG90aW1lKENUQ0xfbW9ub2NsZSwgcmVkdWN0aW9uX21ldGhvZCA9ICJVTUFQIikpCiMgUmVwbGFjZSBJTkYgcHNldWRvdGltZSB3aXRoIDAKcHNldWRvdGltZVssMV0gPC0gZ3N1YihwYXR0ZXJuID0gIkluZiIsIHJlcGxhY2VtZW50ID0gMCwgeCA9IHBzZXVkb3RpbWUkYHBzZXVkb3RpbWUoQ1RDTF9tb25vY2xlLCByZWR1Y3Rpb25fbWV0aG9kID0gIlVNQVAiKWApCnBzZXVkb3RpbWVbLDFdIDwtIGFzLm51bWVyaWMocHNldWRvdGltZSRgcHNldWRvdGltZShDVENMX21vbm9jbGUsIHJlZHVjdGlvbl9tZXRob2QgPSAiVU1BUCIpYCkKQ1RDTCA8LSBBZGRNZXRhRGF0YShvYmplY3QgPSBDVENMLCBtZXRhZGF0YSA9IHBzZXVkb3RpbWUsIGNvbC5uYW1lID0gInBzZXVkb3RpbWVfbW9ub2NsZSIpCgpgYGAKCk5vdGUgdGhhdCB0aGUgYmVsb3cgcGxvdHMgd2VyZSBlZGl0ZWQgdG8gbWFrZSBsYWJlbHMgbW9yZSBvYnZpb3VzIGFuZCB0byBhZ2dyZWdhdGUgcGxvdHMuCgpOb3RlIHRoYXQgbW9ub2NsZSBsZWFybnMgd2hpY2ggY2VsbHMgYXJlIG9uIGEgc2ltaWxhciB0cmFqZWN0b3J5IGFuZCB3aWxsIHBsYWNlIHRoZW0gaW4gY2xvc2UgcHJveGltaXR5IHdoZW4gcGVyZm9ybWluZyBVTUFQLCBjbHVzdGVyaW5nLCBvciBwYXJ0aXRpb25pbmcuIFdoaWxlIHRoZSBVTUFQIHdpbGwgbG9vayBkaWZmZXJlbnQgdGhhbiB0aGF0IHByb2R1Y2VkIGJ5IFNldXJhdCwgdGhlIGdvYWxzIG9mIHRoZSBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gaW4gZWFjaCBpcyBkaWZmZXJlbnQuCgpOb25ldGhlbGVzcywgd2UgY2FuIHNlZSB0aGF0IG1vbm9jbGUgY2x1c3RlcmluZyBpcyBtb3N0bHkgc2ltaWxhciB0byB3aGF0IHdlIGdldCBieSBzdGFuZGFyZCBsZWlkZW4gY2x1c3RlcmluZy4gSW50ZXJlc3RpbmdseSwgbW9ub2NsZSBpZGVudGlmaWVzIGNsdXN0ZXJzIGNlbGxzIGZyb20gbGVpZGVuIGNsdXN0ZXIgMSBhbmQgMyB0b2dldGhlciwgc3VnZ2VzdGluZyB0aGF0IHRoZXJlIG1heSBiZSBzb21lIGNsdXN0ZXIgMSBjZWxscyB0aGF0IHNob3VsZCBiZSBhc3NpZ25lZCB0byBjbHVzdGVyIDMuIAoKTW9zdCBpbXBvcnRhbnRseSwgd2Ugc2VlIHRoYXQgbW9ub2NsZSBpZGVudGlmaWVzIGEgY2xlYXIgcHNldWRvdGltZSB0cmFqZWN0b3J5IHRoYXQgY29ubmVjdHMgbnVtYmF0IGNsb25lIDIgYW5kIDMuIEFuZCB0aGF0IG51bWJhdCBjbG9uZXMgMiBhbmQgMyBjbHVzdGVyIHNlcGFyYXRlbHkgZnJvbSBudW1iYXQgY2xvbmUgMSBhbmQgTkEgY2VsbHMgKGxlaWRlbiBjbHVzdGVyIDIpLiBUaGlzIHByb3ZpZGVzIHN0cm9uZyBldmlkZW5jZSB0aGF0IHdlIGNob3NlIHRoZSBjb3JyZWN0IHJlZmVyZW5jZSBmb3IgbnVtYmF0IGFuYWx5c2lzLCBidXQgdGhhdCBpbiBhZGRpdGlvbiwgdGhlIGluZmVycmVkIGNsb25hbCBldm9sdXRpb25hcnkgcGF0dGVybiAoY2xvbmUgMiAtLT4gMykgbWF5IGluZGVlZCBiZSByZWFsLiBQc3VlZG90aW1lIGFuYWx5c2lzIGZ1cnRoZXIgaWxsdXN0cmF0ZXMgdGhpcyBwb2ludC4KCmBgYHtyfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiL2hvbWUvYm9iYnkvUlN0dWRpby9DVENMX0ltbXVuZVByb2ZpbGluZy9tb25vY2xlL21vbm9jbGVfY29tYmluZWRwbG90cy5wbmciKQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiL2hvbWUvYm9iYnkvUlN0dWRpby9DVENMX0ltbXVuZVByb2ZpbGluZy9tb25vY2xlL21vbm9jbGVfcHN1ZWRvdGltZV9wbG90LnBuZyIpCmBgYAoK